Chapter 15. Ethernet and Networking

15.0. Introduction

Want to share your sensor data? Let other people take control of your Arduino’s actions? Your Arduino can communicate with a broader world over Ethernet and networks. This chapter describes the many ways you can use Arduino with the Internet. It has examples that demonstrate how to build and use web clients and servers and it shows how to use the most common Internet communication protocols with Arduino.

The Internet allows a client (e.g., your web browser) to request information from a server (a web server or other Internet service provider). This chapter contains recipes showing how to make an Internet client that retrieves information from a service such as Google or Yahoo! Other recipes in this chapter show how Arduino can be an Internet server that provides information to clients using Internet protocols and act as a web server that creates pages for viewing in web browsers.

The Arduino Ethernet library supports a range of methods (protocols) that enable your sketches to be an Internet client or a server. The Ethernet library uses the suite of standard Internet protocols, and most of the low-level plumbing is hidden. Getting your clients or servers up and running and doing useful tasks will require some understanding of the basics of network addressing and protocols, and you may want to consult one of the many references available online or one of these introductory books:

Here are some of the key concepts in this chapter. You may want to explore them in more depth than is possible here:

Ethernet

This is the low-level signaling layer providing basic physical message-passing capability. Source and destination addresses for these messages are identified by a Media Access Control (MAC) address. Your Arduino sketch defines a MAC address value that must be unique on your network.

TCP and IP

Transmission Control Protocol (TCP) and Internet Protocol (IP) are core Internet protocols built above Ethernet. They provide a message-passing capability that operates over the global Internet. TCP/IP messages are delivered through unique IP addresses for the sender and receiver. A server on the Internet uses a numeric label (address) that no other server will have so that it can be uniquely identified. This address consists of four bytes, usually represented with dots separating the bytes (e.g., 64.233.187.64 is an IP address used by Google). The Internet uses the Directory Name System (DNS) service to translate the common service name (http://www.google.com) to the numeric IP address, but the standard Arduino Ethernet library does not include the DNS capability. Recipe 15.3 shows how to use a third-party DNS library to add this capability to your sketches.

Local IP addresses

If you have more than one computer connected to the Internet on your home network using a broadband router or gateway, each computer probably uses a local IP address that is provided by your router. The local address is created using a Dynamic Host Configuration Protocol (DHCP) service in your router. The Arduino Ethernet library does not include a DHCP service, so you either need to select a local IP address or use a third-party library that adds DHCP. Most of the recipes in this chapter show a user-selected IP address that you may need to modify to suit your network. Recipe 15.2 shows how the IP address can be obtained automatically using DHCP.

Web requests from a web browser and the resultant responses use Hypertext Transfer Protocol (HTTP) messages. For a web client or server to respond correctly, it must understand and respond to HTTP requests and responses. Many of the recipes in this chapter use this protocol, and referring to one of the references listed earlier for more details will help with understanding how these recipes work in detail.

Web pages are usually formatted using Hypertext Markup Language (HTML). Although it’s not essential to use HTML if you are making an Arduino web server, as Recipe 15.9 illustrates, the web pages you serve can use this capability.

Extracting data from a web server page intended to be viewed by people using a web browser can be a little like finding a needle in a haystack because of all the extraneous text, images, and formatting tags used on a typical page. This task is simplified in the recipes here with a library written for this book, called TextFinder. It is available from the Arduino Playground. TextFinder extracts information from a stream of data. It is used with the Arduino Ethernet library to find particular sequences of characters and to get strings and numeric values.

Web interchange formats have been developed to enable reliable extraction of web data by computer software. XML and JSON are two of the most popular formats, and Recipe 15.5 shows an example of how to do this using Arduino.

15.1. Setting Up the Ethernet Shield

Problem

You want to set up the Ethernet shield to use a hardcoded IP address.

Solution

This sketch is based on the Ethernet client example sketch distributed with Arduino. Check the documentation for your network to ensure that the Arduino IP address (the value of the ip variable) is valid for your network:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168 1, 177 };    // change to a valid address for your network
byte server[] = { 64, 233, 187, 99 }; // Google

                                       // see text for more on IP addressing



Client client(server, 80);

void setup()
{
  Ethernet.begin(mac, ip); // start ethernet using the mac and IP address
  Serial.begin(9600);      // start the serial library:
  delay(1000);             // give the ethernet hardware a second to initialize

  Serial.println("connecting...");

  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0"); // the HTTP request
    client.println();
  } else {
    Serial.println("connection failed");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);  // echo all data received to the Serial Monitor
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

Discussion

This sketch performs a Google search using the word “arduino”. Its purpose is to provide working code that you can use to verify that your network configuration is suitable for the Arduino Ethernet shield.

There are four addresses that must be set up correctly for the sketch to successfully connect and display the results of the search on the Serial Monitor:

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

The MAC address uniquely identifies your Ethernet shield. Every network device must have a different MAC address, and if you use more than one Arduino shield on your network, each must use a different address. If you have a single Ethernet shield, you don’t need to change this:

byte ip[] = { 192, 168 1, 177 };      // change this to a valid address for your
network

The IP address is used to identify something that is communicating on the Internet and must also be unique on your network. The address consists of four bytes, and the range of valid values for each byte depends on how your network is configured. IP addresses are usually expressed with dots separating the bytes—for example, 192.168.1.177. In all the Arduino sketches, commas are used instead of dots because the bytes are stored in an array (see Recipe 2.4).

If your network is connected to the Internet using a router or gateway, you may need to provide the IP address of the gateway when you call the ethernet.begin function. You can find the address of the gateway in the documentation for your router/gateway. Add a line after the IP and server addresses at the top of the sketch with the address of your gateway:

byte gateway[] ={ 192, 168, 1, 254 };  // add this if needed by your router or
gateway

And change the first line in setup to include the gateway address in the startup values for Ethernet:

  Ethernet.begin(mac, ip, gateway);

The server address consists of the 4-byte IP address of the server you want to connect to—in this case, Google. Server IP addresses change from time to time, so you may need to use the ping utility of your operating system to find a current IP address for the server you wish to connect to:

byte server[] = { 64, 233, 187, 99 }; // Google

Note

The line at the top of the sketch that includes <SPI.h> is required for Arduino releases starting at 0019, but not for earlier versions. The code in the sketch here uses a conditional check to enable it to work in any version. See Recipe 17.6 for more on conditional defines.

See Also

The web reference for getting started with the Arduino Ethernet shield is at http://arduino.cc/en/Guide/ArduinoEthernetShield.

15.2. Obtaining Your IP Address Automatically

Problem

The IP address you use for the Ethernet shield must be unique on your network and you would like this to be allocated automatically. You want the Ethernet shield to obtain an IP address from a DHCP server.

Solution

This is the sketch from Recipe 15.1 with the bolded lines added to use the DHCP library:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include "Dhcp.h"         // add this for the DHCP library

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
                                   // IP addressing lines removed
byte server[] = { 209,85,229,104  }; // Google

Client client(server, 80);

void setup()
{
  Serial.begin(9600);
  if(Dhcp.beginWithDHCP(mac) == 1)   // begin method returns 1 if successful
  {
    Serial.println("got IP address, connecting...");
    delay(5000);
  }
  else
  {
    Serial.println("unable to acquire ip address!");
    while(true)
       ;  // do nothing
  }

  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0");
    client.println();
  } else {
    Serial.println("connection failed");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

Discussion

The library distributed with the Arduino release does not support DHCP (at the time of this writing), but you can obtain a third-party library from this book’s website or from http://blog.jordanterrell.com/post/Arduino-DHCP-Library-Version-04.aspx. Copy the contents of the download into the ArduinohardwarelibrariesEthernet folder.

The highlighted lines show the differences from the sketch in Recipe 15.1. There is no IP or gateway address variable; these are acquired from your DHCP server when the sketch starts.

If you want to see the values returned from the DHCP server on the Serial Monitor, use the following setup function:

void setup()
{
  Serial.begin(9600);
  if(Dhcp.beginWithDHCP(mac) == 1)   // begin method returns 1 if successful
  {
    byte buffer[6];
    Serial.println("ip acquired...");

    // show the values returned from the DHCP server
    Dhcp.getLocalIp(buffer);
    Serial.print("ip address: ");
    printArray(&Serial, ".", buffer, 4, 10);

    Dhcp.getSubnetMask(buffer);
    Serial.print("subnet mask: ");
    printArray(&Serial, ".", buffer, 4, 10);

    Dhcp.getGatewayIp(buffer);
    Serial.print("gateway ip: ");
    printArray(&Serial, ".", buffer, 4, 10);

    Dhcp.getDhcpServerIp(buffer);
    Serial.print("dhcp server ip: ");
    printArray(&Serial, ".", buffer, 4, 10);

    Dhcp.getDnsServerIp(buffer);
    Serial.print("dns server ip: ");
    printArray(&Serial, ".", buffer, 4, 10);

    delay(5000);
  }
  else
  {
    Serial.println("unable to acquire ip address!");
    while(true)
       ;  // do nothing
  }
  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0");
    client.println();
  } else {
    Serial.println("connection failed");
  }
}

And add this function to the end of the sketch to produce the formatted output that is sent to the Serial Monitor:

void printArray(Print *output, char* delimeter, byte* data, int len, int base)
{
  char buf[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  for(int i = 0; i < len; i++)
  {
    if(i != 0)
      output->print(delimeter);

    output->print(itoa(data[i], buf, base));
  }

  output->println();
}

Running this sketch will display the IP configuration information received from your DHCP server on the Serial Monitor:

IP address: 192.168.1.177
subnet mask: 255.255.255.0
gateway IP: 192.168.1.254
DHCP server IP: 192.168.1.254
DNS server IP: 192.168.1.254

15.3. Resolving Hostnames to IP Addresses (DNS)

Problem

You want to use a server name—for example, yahoo.com—rather than a specific IP address. Web providers often have a range of IP addresses used for their servers and a specific address may not be in service when you need to connect.

Solution

You can use DNS to look up a valid IP address for the name you provide. This example uses DNS code from Matt Robertson at http://kegger.googlecode.com/files/Ethernet.zip:

/*
 * WebClientDNS sketch
 */

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include "Ethernet_dns.h"
#include "Dns.h"   // uses DNS library from Matt Robertson

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = {192, 168, 1, 177 };    // change to a valid address for your network


byte gateway[] ={192, 168, 1, 254 };  // add this if you use a router or gateway
// see text for more on IP addressing
byte subnet[] ={255, 255, 255, 0 };   // this defines the subnet address


byte ipBuffer[6];  // this will get the server IP address from DNS

Client client(ipBuffer, 80);
DnsClass Dns;

//Client client(ipBuffer, server, 80);

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip, gateway, subnet);

  //// Do DNS Lookup
  Serial.println("getting server address");
  Dns.init("google.com", ipBuffer);  //Buffer has IP address of the DNS server
  Dns.resolve();

  int results;
  while(!(results=Dns.finished())) ;  //wait for DNS to resolve the name

  if(results != 1){
    Serial.print("DNS Error code: ");
    Serial.print(results,DEC);
    while(true)
      ;  // do nothing
  }

  delay(5000);


  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0");
    client.println();
  }
  else {
    Serial.println("connection failed");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

Discussion

This code is similar to the code in Recipe 15.1; it does a Google search for “arduino”. But in this version it is not necessary to provide the Google IP address—it is obtained through a request to the Internet DNS service.

The request is achieved through these function calls:

  Dns.init("google.com", ipBuffer);  //Buffer contains the IP address of the DNS
server
  Dns.resolve();
  while(!(results=Dns.finished())) ;  //wait for DNS to resolve the name

The Dns.Init function is called with two parameters: the server (host) name to look up and the character array to hold the IP address if the lookup is successful. Dns.resolve sends the request, and Dns.finished returns the status of the reply. Here are the values that can be returned from Dns.finished:

  1 = success
  2 = No DNS records found
  3 = timeout
  greater than 16 is an error code provided by the DNS server

The sketch checks the value returned from Dns.finished—if it is 1, the variable ipBuffer will contain a valid IP address; otherwise, it is an error that is printed to the Serial Monitor.

You can view the IP address using the following code:

Dns.getIP(ipBuffer);  //buffer now contains the IP address for server given in
Dns.Init()
Serial.print("Google address: ");
printIp(ipBuffer);

The printIp function is:

void printIp(byte *rawData)
{
  for(int i=0; i < 4; i++){
     Serial.print(rawData[i],DEC);
     if(i < 3)
       Serial.print('.'),
  }
  Serial.println();
}

You can use DNS and DHCP together. Here is the sketch with DHCP functionality added (see Recipe 15.2):

/*
 * WebClientDHCP_DNS sketch
 */
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include "Ethernet_dns.h"
#include "Dhcp.h"         // add this for the DHCP library
#include "Dns.h"   // uses DNS library from Matt Robertson

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

byte ipBuffer[6];
Client client(ipBuffer, 80);
DnsClass Dns;

//Client client(ipBuffer, server, 80);

void setup()
{
  Serial.begin(9600);
  if(Dhcp.beginWithDHCP(mac) == 1)   // begin method returns 1 if successful
  {
    Serial.println("got IP address");
        //// Do DNS Lookup
    Serial.println("getting server address");
    Dns.init("google.com", ipBuffer);  //Buffer has IP address of the DNS server
    Dns.resolve();

    int results;
    while(!(results=Dns.finished())) ;  //wait for DNS to resolve the name

    if(results != 1){
      Serial.print("DNS Error code: ");
      Serial.print(results,DEC);
      while(true)
         ;  // do nothing
    }
    delay(5000);
  }
  else
  {
    Serial.println("unable to acquire ip address!");
    while(true)
       ;  // do nothing
  }
  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0");
    client.println();
  } else {
    Serial.println("connection failed");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();
    for(;;)
      ;
  }
}

15.4. Requesting Data from a Web Server

Problem

You want Arduino to get data from a web server. For example, you want to find and use specific values returned from a web server.

Solution

This sketch uses Google Calculator to convert 50 kilometers to miles. It sends the query “what+is+50+km+in+mi” and prints the result to the Serial Monitor:

/*
 * SimpleClientwFinder sketch
 *
 */

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,1,177 };
//byte gateway[] ={ 192, 168, 1, 254 };
//byte subnet[] ={ 255, 255, 255, 0 };

byte server[] = { 173,194,33,104 }; // Google

Client client(server, 80);
TextFinder finder( client);

int result; // the result of the google calculation

void setup()
{
  Ethernet.begin(mac, ip);
  Serial.begin(9600);
  delay(2000);
  Serial.println("connecting...");

}

void loop()
{
  if (client.connect()) {
    Serial.print("connected... ");
    client.println("GET /search?hl=en&q=what+is+50+km+in+mi HTTP/1.1");
    client.println();
  } else {
    Serial.println("connection failed");
  }
  if (client.connected()) {
    if(finder.find("<b>50 kilometers")){
      if(finder.find("=")){
         result = finder.getValue();
         Serial.println(result);
      }
    }
    else
       Serial.println("result not found");
    client.stop();
    delay(10000); // check again in 10 seconds
  }
  else {
    Serial.println();
    Serial.println("not connected");
    client.stop();
    delay(1000);
  }
}

Discussion

The sketch assumes the results will be returned in bold (using the HTML <b> tag) followed by the value given in the query and the word kilometers.

The searching is done using the TextFinder library described in this chapter’s introduction. The find method searches through the received data and returns true if it finds the target string. The code looks for text associated with the reply. In this example, it tries to find “<b>50 kilometers” using this line:

if(finder.find("<b>50 kilometers")){

finder is used again to find the equals sign (=) that precedes the numerical value of the result.

The result is obtained using the getvalue method and is printed to the Serial Monitor.

getValue returns an integer value; if you want to get a floating-point value, use getFloat instead:

         Float floatResult = finder.getFloat();
         Serial.println(floatResult);

If you want your searches to be robust, you need to look for a unique tag that will only be found preceding the data you want. This is easier to achieve on pages that use unique tags for each field, such as this example that gets the Google stock price from Google Finance and writes the value to analog output pin 0 and to the Serial Monitor:

/*
 * WebClientGoogleFinance sketch
 * get the stock value for google and write to analog pin.
 */
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = {  192, 168,1,177 };
byte gateway[] ={ 192, 168, 1, 254 };
byte subnet[] ={ 255, 255, 255, 0 };

byte server[] = {209,85,229,147 };  // google
Client client(server, 80);

TextFinder  finder( client );
float value;

void setup()
{
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);
  delay(2000);
}

void loop()
{
  Serial.print("Connecting...");
  if (client.connect()) {
    client.println("GET //finance?q=google HTTP/1.0"); // todo
    client.println("User-Agent: AVR ethernet");
    client.println();
  }
  else
  {
    Serial.println("connection failed");
  }
  if (client.connected()) {
     if(finder.find("<span class="pr">"))
     {
       finder.find(">");  // seek past the next '>'
       value = finder.getFloat();
       Serial.println(value);
       analogWrite(0, value);
     }
    else
      Serial.print("Could not find field");
  }
  else {
    Serial.println("Disconnected");
  }
  client.stop();
  client.flush();
  delay(5000); // 5 seconds between each connect attempt
}

These examples use the GET command to request a specific page. Some web requests need data to be sent to the server within the body of the message, because there is more data to be sent than can be handled by the GET command. These requests are handled using the POST command. Here is an example of POST that uses the Babel Fish language translation service to translate text from Italian into English:

/*
 * WebClient_Babelfish sketch
 * Uses Post to get data from a web server
 *
 */
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = {  192, 168,1,177 };
byte gateway[] ={ 192, 168, 1, 254 };
byte subnet[] ={ 255, 255, 255, 0 };

byte server[] = {66,196,80,202 }; //babelfish.yahoo.com

Client client(server, 80);

// the text to translate
char * transText = "trtext=Ciao+mondo+da+Arduino.&lp=it_en";

char buffer [31];  // room for 30 characters and the terminating null

TextFinder  finder(client);

void setup()
{
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);
  delay(3000);
}

void loop()
{
  postPage( "/translate_txt", transText);
  delay(5000);
}

void postPage(char *webPage, char *parameter){
  if (client.connect()) {
    client.print("POST ");
    client.print(webPage);
    client.println("  HTTP/1.0");
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.println("Host: babelfish.yahoo.com");
    client.print("Content-Length: ");
    client.println(strlen(parameter));
    client.println();
    client.println(parameter);
  }
  else {
    Serial.println(" connection failed");
  }
  if (client.connected()) {
     finder.find("<div id="result">");
     finder.getString( ">", "<" ,buffer, sizeof(buffer));
     Serial.println(buffer);
  }
  else {
    Serial.println("Disconnected");
  }
  client.stop();
  client.flush();
}

Sites such as Google Weather and Google Finance generally keep the tags used to identify fields unchanged. But if some future update to a site does change the tags you are searching for, your sketch will not function correctly until you correct the search code. A more reliable way to extract data from a web server is to use a formal protocol, such as XML or JSON. The next recipe shows how to extract information from a site that uses XML.

15.5. Requesting Data from a Web Server Using XML

Problem

You want to retrieve data from a site that publishes information in XML format. For example, you want to use values from specific fields in one of the Google API services.

Solution

This sketch retrieves the weather in London from the Google Weather site. It uses the Google XML API:

/*
 * SimpleClientGoogleWeatherDHCP
 * gets xml data from http://www.google.com/ig/api?weather=london,uk
 * reads temperature from field:  <temp_f data="66" />
 * writes temperature  to analog output port.
 */

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include "Dhcp.h"  //from: http://blog.jordanterrell.com/post/
Arduino-DHCP-Library-Version-04.aspx
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte server[] = {209,85,229,104 }; // Google

Client client(server, 80);

TextFinder  finder( client );

void setup()
{
  Serial.begin(9600);
  if(Dhcp.beginWithDHCP(mac) == 1) {  // begin method returns 1 if successful
    Serial.println("got IP address, connecting...");
    delay(5000);
  }
  else {
    Serial.println("unable to acquire ip address!");
    while(true)
       ;  // do nothing
  }
}


void loop()
{
  if (client.connect()) {
  // get google weather for London
    client.println("GET /ig/api?weather=london,uk HTTP/1.0");  
    client.println();
  }
  else {
    Serial.println(" connection failed");
  }
  if (client.connected()) {
     // get temperature in fahrenheit (use field "<temp_c data="" for Celsius)
     if(finder.find("<temp_f data=") )
     {
        int temperature = finder.getValue();
        analogWrite(3, temperature);      // write value to analog port
        Serial.print("Temperature is ");  // and echo it to the serial port
        Serial.println(temperature);
     }
    else
      Serial.print("Could not find temperature field");
  }
  else {
    Serial.println("Disconnected");
  }
  client.stop();
  client.flush();
  delay(60000); // wait a minute before next update
}

Each field is preceded by a tag, and the one indicating the temperature in Fahrenheit on Google Weather is "<temp_f data=".

On this site, if you want the temperature in Celsius you would look for the tag "<temp_c data=".

You will need to consult the documentation for the page you are interested in to find the relevant tag for the data you want.

You select the page through the information sent in your GET statement. This also depends on the particular site, but in the preceding example, the city is selected by the text after the equals sign following the GET statement. For example, to change the location from London to New York, change:

client.println("GET /ig/api?weather=london,uk HTTP/1.0");  // weather for London

to:

client.println("GET /ig/api?weather=New York,NY HTTP/1.0"); // weather for
New York

You can use a variable if you want the location to be selected under program control:

char *cityString[4] = { "London", "New%20York", "Rome", "Tokyo"};
int   city;

void loop()
{
  city = random(4); // get a random city
  if (client.connect()) {
    client.print("GET /ig/api?weather=");
    client.print(cityString[city]); // print one of 4 random cities
    client.println(" HTTP/1.0");
    client.println();
  }
  else {
    Serial.println(" connection failed");
  }
  if (client.connected()) {
     // get temperature in fahrenheit (use field "<temp_c data="" for Celsius)
     if(finder.find("<temp_f data=") )
     {
        int temperature = finder.getValue();
        analogWrite(3, temperature);      // write value to the analog port
        Serial.print(cityString[city]);
        Serial.print(" temperature is ");  // and echo it to the serial port
        Serial.println(temperature);
     }
    else
      Serial.println("Could not find temperature field");
  }
// the remainder of the code is the same as the previous sketch

Note

Information sent in URLs cannot contain spaces, which is why New York is written as “New%20York”. The encoding to indicate a space is %20. Your browser does the encoding before it sends a request, but on Arduino you must translate spaces to %20 yourself.

15.6. Setting Up an Arduino to Be a Web Server

Problem

You want Arduino to serve web pages. For example, you want to use your web browser to view the values of sensors connected to Arduino analog pins.

Solution

This is the standard Arduino WebServer example sketch distributed with Arduino that shows the value of the analog input pins. This recipe explains how this sketch works and how it can be extended:

/*
 * Web Server
 *
 * A simple web server that shows the value of the analog input pins.
 */
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 177};

Server server(80);

void setup()
{
  Ethernet.begin(mac, ip);
  server.begin();
}

void loop()
{
  Client client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean current_line_is_blank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        // if we've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so we can send a reply
        if (c == '
' && current_line_is_blank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();

          // output the value of each analog input pin
          for (int i = 0; i < 6; i++) {
            client.print("analog input ");
            client.print(i);
            client.print(" is ");
            client.print(analogRead(i));
            client.println("<br />");
          }
          break;
        }
        if (c == '
') {
          // we're starting a new line
          current_line_is_blank = true;
        } else if (c != '
') {
          // we've gotten a character on the current line
          current_line_is_blank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

Discussion

As discussed in Recipe 15.1, all of the sketches using the Ethernet library need a unique MAC address and IP address. The IP address you assign in this sketch determines the address of the web server. In this example, typing 192.168.1.177 into your browser’s address bar should display a page showing the values on analog input pins 0 through 6 (see Chapter 5 for more on the analog ports).

As described in this chapter’s introduction, 192.168.1.177 is a local address that is only visible on your local network. If you want to expose your web server to the entire Internet, you will need to configure your router to forward incoming messages to Arduino. The technique is called port forwarding and you will need to consult the documentation for your router to see how to set this up. (For more on port forwarding in general, see SSH, The Secure Shell: The Definitive Guide by Daniel J. Barrett, Richard E. Silverman, and Robert G. Byrnes [O’Reilly].)

Warning

Configuring your Arduino Ethernet board to be visible on the Internet makes the board accessible to anyone with the IP address. The Arduino Ethernet library does not offer secure connections, so be careful about the information you expose.

The two lines in setup initialize the Ethernet library and configure your web server to the IP address you provide. The loop waits for and then processes each request received by the web server:

  Client client = server.available();

The client class here is actually the web server—it processes messages for the IP address you gave the server.

if (client) tests that the client has been successfully started.

while (client.connected()) tests if the web server is connected to a client making a request.

client.available() and client.read() check if data is available, and read a byte if it is. This is similar to Serial.available(), discussed in Chapter 4, except the data is coming from the Internet rather than the serial port. The code reads data until it finds the first line with no data, signifying the end of a request. An HTTP header is sent using the client.println commands followed by the printing of the values of the analog ports.

15.7. Handling Incoming Web Requests

Problem

You want to control digital and analog outputs with Arduino acting as a web server. For example, you want to control the values of specific pins through parameters sent from your web browser.

Solution

This sketch reads requests sent from a browser and changes the values of digital and analog output ports as requested.

The URL (browser request) contains one or more fields starting with the word pin followed by a D for digital or A for analog and the pin number. The value for the pin follows an equals sign.

For example, sending http://192.168.1.177/?pinD2=1 from your browser’s address bar turns digital pin 2 on; http://192.168.1.177/?pinD2=0 turns pin 2 off. (See Chapter 7 if you need information on connecting LEDs to Arduino pins.)

Figure 15-1 shows what you will see on your web browser when connected to the web server code that follows:

/*
 * WebServerParsing
 *
 * Respond to requests in the URL to change digital and analog output ports
 * show the number of ports changed and the value of the analog input pins.
 *
*/
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

##include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,1,177 };

Server server(80);

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.println("ready");
}

void loop()
{
  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    while (client.connected()) {
      if (client.available()) {
        // counters to show the number of pin change requests
        int digitalRequests = 0;  
        int analogRequests = 0;
        if( finder.find("GET /") ) {
          // find tokens starting with "pin" and stop on the first blank line
          while(finder.findUntil("pin", "

")){
            char type = client.read(); // D or A
            int pin = finder.getValue();
            int val = finder.getValue();
            if( type == 'D') {
              Serial.print("Digital pin ");
              pinMode(pin, OUTPUT);
              digitalWrite(pin, val);
              digitalRequests++;
            }
            else if( type == 'A'){
              Serial.print("Analog pin ");
              analogWrite(pin, val);
              analogRequests++;
            }
            else {
              Serial.print("Unexpected type ");
              Serial.print(type);
            }
            Serial.print(pin);
            Serial.print("=");
            Serial.println(val);
          }
        }
        Serial.println();

        // the findUntil has detected the blank line (a lf followed by cr)
        // so the http request has ended and we can send a reply
        // send a standard http response header
        client.println("HTTP/1.1 200 OK");
        client.println("Content-Type: text/html");
        client.println();

        // output the number of pins handled by the request
        client.print(digitalRequests);
        client.print(" digital pin(s) written");
        client.println("<br />");
        client.print(analogRequests);
        client.print(" analog pin(s) written");
        client.println("<br />");
        client.println("<br />");


        // output the value of each analog input pin
        for (int i = 0; i < 6; i++) {
          client.print("analog input ");
          client.print(i);
          client.print(" is ");
          client.print(analogRead(i));
          client.println("<br />");
        }
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}
Browser page displaying output created by this recipe’s Solution
Figure 15-1. Browser page displaying output created by this recipe’s Solution

Discussion

This is what was sent: http://192.168.1.177/?pinD2=1. Here is how the information is broken down: Everything before the question mark is treated as the address of the web server (192.168.1.177 in this example; this address is the IP address set at the top of the sketch for the Arduino board). The remaining data is a list of fields, each beginning with the word pin followed by a D indicating a digital pin or A indicating an analog pin. The numeric value following the D or A is the pin number. This is followed by an equals sign and finally the value you want to set the pin to. pinD2=1 sets digital pin 2 HIGH. There is one field per pin, and subsequent fields are separated by an ampersand. You can have as many fields as there are Arduino pins you want to change.

The sketch can be extended to handle multiple parameters by using ampersands to separate multiple fields. For example: http://192.168.1.177/?pinD2=1&pinD3=0&pinA9=128&pinA11=255

Each field within the ampersand is handled as described earlier. You can have as many fields as there are Arduino pins you want to change.

15.8. Handling Incoming Requests for Specific Pages

Problem

You want to have more than one page on your web server; for example, to show the status of different sensors on different pages.

Solution

This sketch looks for requests for pages named “analog” or “digital” and displays the pin values accordingly:

/*
 * WebServerMultiPage
 *
 * Respond to requests in the URL to view digital and analog output ports
 *
 * http://192.168.1.177/analog/   displays analog pin data
 * http://192.168.1.177/digital/  displays digital pin data
*/
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] =  { 192,168,1,177 };

char buffer[8]; // make this buffer big enough to hold requested page names

Server server(80);

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.println("Ready");
}

void loop()
{
  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    while (client.connected()) {
      if (client.available()) {
        if( finder.find("GET ") ) {
          // look for the page name
          if(finder.getString( "/", "/", buffer, sizeof(buffer) )) 
          {
            if(strcmp(buffer, "analog") == 0)
              showAnalog(client);
            else if(strcmp(buffer, "digital") == 0)
              showDigital(client);
            else
              unknownPage(client, buffer);
          }
        }
        Serial.println();
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

void showAnalog(Client client)
{
  Serial.println("analog");
  sendHeader(client);
  client.println("<h1>Analog Pins</h1>");
  // output the value of each analog input pin

  for (int i = 0; i < 6; i++) {
    client.print("analog pin ");
    client.print(i);
    client.print(" = ");
    client.print(analogRead(i));
    client.println("<br />");
  }
}

void showDigital(Client client)
{
  Serial.println("digital");
  sendHeader(client);
  client.println("<h1>Digital Pins</h1>");
  // show the value of digital pins
  for (int i = 2; i < 8; i++) {
    pinMode(i, INPUT);
    client.print("digital pin ");
    client.print(i);
    client.print(" is ");
    if(digitalRead(i) == LOW)
      client.print("LOW");
    else
      client.print("HIGH");
    client.println("<br />");
  }
  client.println("</body></html>");
}

void unknownPage(Client client, char *page)
{
  Serial.print("Unknown : ");
  Serial.println("page");

  sendHeader(client);
  client.println("<h1>Unknown Page</h1>");
  client.println(page);
  client.println("</body></html>");
}

void sendHeader(Client client)
{
  // send a standard http response header
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.println("<html><head><title>Web server multi-page Example</title>");
  client.println("<body>");
}

Discussion

You can test this from your web browser by typing http://192.168.1.177/analog/ or http://192.168.1.177/digital/ (if you are using a different IP address for your server, change the URL to match).

Figure 15-2 shows the expected output.

Browser output showing digital pin values
Figure 15-2. Browser output showing digital pin values

The sketch looks for the “/” character to determine the end of the page name. The server will report an unknown page if the “/” character does not terminate the page name.

Here is an enhancement that includes some code from Recipe 15.7 to allow control of Arduino pins from a private page. Here is the new loop code:

void loop()
{
  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    while (client.connected()) {
      if (client.available()) {
        if( finder.find("GET ") ) {
          if(finder.getString( "/", "/", buffer, sizeof(buffer) )) // look for
the page name
          {
            if(strcmp(buffer, "analog") == 0)
              showAnalog(client);
            else if(strcmp(buffer, "digital") == 0)
              showDigital(client);
            else if(strcmp(buffer, "private") == 0)  // add this check
              showPrivate(client);                   // and this function call

            else
              unknownPage(client, buffer);
          }
        }
        Serial.println();
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

Here is the showPrivate function:

void showPrivate(Client client, TextFinder finder)
{
  Serial.println("private");
  sendHeader(client);
  // find tokens starting with "pin" and stop on the first blank line
  while(finder.findUntil("pin", "

")){
    char type = client.read(); // D or A
    int pin = finder.getValue();
    int val = finder.getValue();
    if( type == 'D') {
      Serial.print("Digital pin ");
      pinMode(pin, OUTPUT);
      digitalWrite(pin, val);

    }
    else if( type == 'A'){
      Serial.print("Analog pin ");
      analogWrite(pin, val);

    }
    else {
      Serial.print("Unexpected type ");
      Serial.print(type);
    }
    Serial.print(pin);
    Serial.print("=");
    Serial.println(val);
  }
}

Sending http://192.168.1.177/private/?pinA5=128 from your browser’s address bar writes the value 128 to analog output pin 5.

15.9. Using HTML to Format Web Server Responses

Problem

You want to use HTML elements such as tables and images to improve the look of web pages served by Arduino. For example, you want the output from Recipe 15.8 to be rendered in an HTML table.

Solution

Figure 15-3 shows how the web server in this recipe’s Solution formats the browser page to display pin values. (You can compare this to the unformatted values shown in Figure 15-2.)

Browser pages using HTML formatting
Figure 15-3. Browser pages using HTML formatting

This sketch shows the functionality from Recipe 15.8 with output formatted using HTML:

/*
 * WebServerMultiPageHTML
 *
 * Display analog and digital pin values using HTML formatting
*/
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] =  { 192,168,1,177 };

char buffer[8]; // make this buffer big enough to hold requested page names

Server server(80);

void setup()
{
  Ethernet.begin(mac, ip);
  server.begin();
  pinMode(13,OUTPUT);
  for(int i=0; i < 3; i++)
  {
     digitalWrite(13,HIGH);
     delay(500);
     digitalWrite(13,LOW);
     delay(500);
  }
}

void loop()
{
  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    while (client.connected()) {
      if (client.available()) {
        if( finder.find("GET ") ) {
          // look for the page name
          if(finder.getString( "/", "/", buffer, sizeof(buffer) )) 
          {
            if(strcasecmp(buffer, "analog") == 0)
              showAnalog(client);
            else if(strcasecmp(buffer, "digital") == 0)
              showDigital(client);
            else if(strcmp(buffer, "private")== 0)
              showPrivate(client, finder);
            else
              unknownPage(client, buffer);
          }
        }
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

void showAnalog(Client client)
{
  sendHeader(client,"Multi-page: Analog");
  client.println("<h2>Analog Pins</h2>");
  client.println("<table border='1' >");
  for (int i = 0; i < 6; i++) {
    // output the value of each analog input pin
    client.print("<tr><td>analog pin ");
    client.print(i);
    client.print(" </td><td>");
    client.print(analogRead(i));
    client.println("</td></tr>");
  }
  client.println("</table>");
  client.println("</body></html>");
}

void showDigital(Client client)
{
  sendHeader(client,"Multi-page: Digital");
  client.println("<h2>Digital Pins</h2>");
  client.println("<table border='1'>");
  for (int i = 2; i < 8; i++) {
    // show the value of digital pins
    pinMode(i, INPUT);
    digitalWrite(i, HIGH); // turn on pull-ups
    client.print("<tr><td>digital pin ");
    client.print(i);
    client.print(" </td><td>");
    if(digitalRead(i) == LOW)
      client.print("Low");
    else
      client.print("High");
    client.println("</td></tr>");
  }
  client.println("</table>");
  client.println("</body></html>");
}

void showPrivate(Client client, TextFinder finder)
{
  sendHeader(client,"Multi-page: Private");
  // find tokens starting with "pin" and stop on the first blank line
  while(finder.findUntil("pin", "

")){
    char type = client.read(); // D or A
    int pin = finder.getValue();
    int val = finder.getValue();
    if( type == 'D') {
      pinMode(pin, OUTPUT);
      digitalWrite(pin, val);

    }
    else if( type == 'A'){
      analogWrite(pin, val);
    }
    else {
     client.print(Serial.print("Unexpected type ");
     client.println(type);
    }
  }
}

void unknownPage(Client client, char *page)
{
  sendHeader(client, "Unknown Page");
  client.println("<h1>Unknown Page</h1>");
  client.println(page);
  client.println("</body></html>");
}

void sendHeader(Client client, char *title)
{
  // send a standard http response header
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.print("<html><head><title>");
  client.println(title);
  client.println("</title><body>");
}

Discussion

The same information is provided as in Recipe 15.8, but here the data is formatted using an HTML table. The following code indicates that the web browser should create a table with a border width of 1:

  client.println("<table border='1' >");

The for loop defines the table data cells with the <td> tag and the row entries with the <tr> tag. The following code places the string "analog pin" in a cell starting on a new row:

client.print("<tr><td>analog pin ");

This is followed by the value of the variable i:

client.print(i);

The next line contains the tag that closes the cell and begins a new cell:

    client.print(" </td><td>");

This writes the value returned from analogRead into the cell:

    client.print(analogRead(i));

The tags to end the cell and end the row are written as follows:

    client.println("</td></tr>");

The for loop repeats this until all six analog values are written. Any of the books mentioned in Series 1 configuration or one of the many HTML reference sites can provide more details on HTML tags.

See Also

Learning Web Design by Jennifer Niederst Robbins (O’Reilly)

Web Design in a Nutshell by Jennifer Niederst Robbins (O’Reilly)

HTML & XHTML: The Definitive Guide by Chuck Musciano and Bill Kennedy (O’Reilly)

15.10. Serving Web Pages Using Forms (POST)

Problem

You want to create web pages with forms that allow users to select an action to be performed by Arduino. Figure 15-4 shows the web page created by this recipe’s Solution.

Web form with buttons
Figure 15-4. Web form with buttons

Solution

This sketch creates a web page that has a form with buttons. Users navigating to this page will see the buttons in the web browser and the Arduino web server will respond to the button clicks. In this example, the sketch turns a pin on and off depending on which button is pressed:

/*
 * WebServerPost sketch
 *
 */
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] =  { 192,168,1,177 };

char buffer[8]; // buffer holding the requested page name

Server server(80);

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  server.begin();
  delay(3000);
  Serial.println("Ready");
}

void loop()
{
  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    int type = 0;
    while (client.connected()) {
      if (client.available()) {
        // GET, POST, or HEAD
        if(finder.getString("","/", buffer,sizeof(buffer))){ 
          if(strcmp(buffer,"POST ") == 0){
            finder.find("

"); // skip to the body
            // find string starting with "pin", stop on first blank line
            // the POST parameters expected in the form pinDx=Y
            // where x is the pin number and Y is 0 for LOW and 1 for HIGH
            while(finder.findUntil("pinD", "

")){
              int pin = finder.getValue();       // the pin number
              int val = finder.getValue();       // 0 or 1
              pinMode(pin, OUTPUT);
              digitalWrite(pin, val);
            }
          }
          sendHeader(client,"Post example");
          //create HTML button to control pin 8
          client.println("<h2>Click buttons to turn pin 8 on or off</h2>");
          client.print("<form action='/' method='POST'><p><input type='hidden'
name='pinD8'");
          client.println(" value='0'><input type='submit' value='Off'/></form>");
          //create HTML button to turn on pin 8
          client.print("<form action='/' method='POST'><p><input type='hidden'
name='pinD8'");
          client.print(" value='1'><input type='submit' value='On'/></form>");
          client.println("</body></html>");
          client.stop();
        }
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

void sendHeader(Client client, char *title)
{
  // send a standard http response header
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.print("<html><head><title>");
  client.print(title);
  client.println("</title><body>");
}

Discussion

A web page with a user interface form consists of HTML tags that identify the controls (buttons, checkboxes, labels, etc.) that comprise the user interface. This recipe uses buttons to provide the user interface.

These lines create a form with a button named pinD8 that is labeled “OFF”, which will send back a value of 0 (zero) when clicked:

client.print("<form action='/' method='POST'><p><input type='hidden'
name='pinD8'");
client.println(" value='0'><input type='submit' value='Off'/></form>");

When the server receives a request from a browser, it looks for the "POST " string to identify the start of the posted form:

if(strcmp(buffer,"POST ") == 0) // find the start of the posted form

   finder.find("

"); // skip to the body
   // find parameters starting with "pin" and stop on the first blank line
   // the POST parameters expected in the form pinDx=Y
   // where x is the pin number and Y is 0 for LOW and 1 for HIGH

If the OFF button was pressed, the received page will contain the string pinD8=0, or pinD8=1 for the ON button.

The sketch searches until it finds the button name (pinD):

            while(finder.findUntil("pinD", "

"))

The findUntil method in the preceding code will search for “pinD” and stop searching at the end of a line ( is the newline carriage return sent by the web browser at the end of a form).

The number following the name pinD is the pin number:

              int pin = finder.getValue();       // the pin number

And the value following the pin number will be 0 if button OFF was pressed or 1 if button ON was pressed:

              int val = finder.getValue();       // 0 or 1

The value received is written to the pin after setting the pin mode to output:

              pinMode(pin, OUTPUT);
              digitalWrite(pin, val);

More buttons can be added by inserting tags for the additional controls. The following lines add another button to turn on digital pin 9:

//create HTML button to turn on pin 9
client.print("<form action='/' method='POST'><p><input type='hidden'
name='pinD9'");
client.print(" value='1'><input type='submit' value='On'/></form>");

15.11. Serving Web Pages Containing Large Amounts of Data

Problem

Your web pages require more memory than you have available, so you want to use program memory (also known as progmem or flash memory) to store data (see Recipe 17.4).

Solution

This sketch combines the POST code from Recipe 15.10 with the HTML code from Recipe 15.9 and adds new code to access text stored in progmem. As in Recipe 15.9, the server can display analog and digital pin status and turn digital pins on and off (see Figure 15-5):

Web page with LED images
Figure 15-5. Web page with LED images
/*
 * WebServerMultiPageHTMLProgmem sketch
 *
 * Respond to requests in the URL to change digital and analog output ports
 * show the number of ports changed and the value of the analog input pins.
 *
 * http://192.168.1.177/analog/   displays analog pin data
 * http://192.168.1.177/digital/  displays digital pin data
 * http://192.168.1.177/private/  allows changing digital pin data
 *
 * Progmem code derived from webduino library by Ben Combee and Ran Talbott
 */

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>
#include <avr/pgmspace.h> // for progmem
#define P(name)   static const prog_uchar name[] PROGMEM  // declare a static
string

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] =  { 192,168,1,177 };

char buffer[8]; // make this buffer big enough to hold requested page names

Server server(80);

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  server.begin();
  delay(3000);
  Serial.println("Ready");
}

void loop()
{

  Client client = server.available();
  if (client) {
    TextFinder  finder(client );
    int type = 0;
    while (client.connected()) {
      if (client.available()) {
        // GET, POST, or HEAD
        if(finder.getString("","/", buffer,sizeof(buffer))){ 
          if(strcmp(buffer, "GET ") == 0 )
            type = 1;
          else if(strcmp(buffer,"POST ") == 0)
            type = 2;
          Serial.print("Type = ");
          Serial.println(type);
          // look for the page name
          if(finder.getString( "", "/", buffer, sizeof(buffer) )) 
          {
            Serial.print(buffer);
            Serial.print("|");
            if(strcasecmp(buffer, "analog") == 0)
              showAnalog(client);
            else if(strcasecmp(buffer, "digital") == 0)
              showDigital(client);
            else if(strcmp(buffer, "private")== 0)
              showPrivate(client, finder, type == 2);
            else
              unknownPage(client, buffer);
          }
        }
        Serial.println();
        break;
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

void showAnalog(Client client)
{
  Serial.println("analog");
  sendHeader(client,"Multi-page example-Analog");
  client.println("<h1>Analog Pins</h1>");
  // output the value of each analog input pin

  client.println("<table border='1' >");
  for (int i = 0; i < 6; i++) {
    client.print("<tr><td>analog pin ");
    client.print(i);
    client.print(" </td><td>");
    client.print(analogRead(i));
    client.println("</td></tr>");
  }
  client.println("</table>");
  client.println("</body></html>");
}

// mime encoded data for the led on and off images:
// see: http://www.motobit.com/util/base64-decoder-encoder.asp
P(led_on) =  "<img src="data:image/jpg;base64,"
"/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAHgAA/+4ADkFkb2JlAGTAAAAAAf/b"
"AIQAEAsLCwwLEAwMEBcPDQ8XGxQQEBQbHxcXFxcXHx4XGhoaGhceHiMlJyUjHi8vMzMvL0BAQEBA"
"QEBAQEBAQEBAQAERDw8RExEVEhIVFBEUERQaFBYWFBomGhocGhomMCMeHh4eIzArLicnJy4rNTUw"
"MDU1QEA/QEBAQEBAQEBAQEBA/8AAEQgAGwAZAwEiAAIRAQMRAf/EAIIAAAICAwAAAAAAAAAAAAAA"
"AAUGAAcCAwQBAAMBAAAAAAAAAAAAAAAAAAACBAUQAAECBAQBCgcAAAAAAAAAAAECAwARMRIhQQQF"
"UWFxkaHRMoITUwYiQnKSIxQ1EQAAAwYEBwAAAAAAAAAAAAAAARECEgMTBBQhQWEiMVGBMkJiJP/a"
"AAwDAQACEQMRAD8AcNz3BGibKie0nhC0v3A+teKJt8JmZEdHuZalOitgUoHnEpQEWtSyLqgACWFI"
"nixWiaQhsUFFBiQSbiMvvrmeCBp27eLnG7lFTDxs+Kra8oOyium3ltJUAcDIy4EUMN/7Dnq9cPMO"
"W90E9kxeyF2d3HFOQ175olKudUm7TqlfKqDQEDOFR1sNqtC7k5ERYjndNPFSArtvnI/nV+ed9coI"
"ktd2BgozrSZO3J5jVEXRcwD2bbXNdq0zT+BohTyjgPp5SYdPJZ9NP2jsiIz7vhjLohtjnqJ/ouPK"
"co//2Q=="
""/>";

P(led_off) = "<img src="data:image/jpg;base64,"
"/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAHgAA/+4ADkFkb2JlAGTAAAAAAf/b"
"AIQAEAsLCwwLEAwMEBcPDQ8XGxQQEBQbHxcXFxcXHx4XGhoaGhceHiMlJyUjHi8vMzMvL0BAQEBA"
"QEBAQEBAQEBAQAERDw8RExEVEhIVFBEUERQaFBYWFBomGhocGhomMCMeHh4eIzArLicnJy4rNTUw"
"MDU1QEA/QEBAQEBAQEBAQEBA/8AAEQgAHAAZAwEiAAIRAQMRAf/EAHgAAQEAAwAAAAAAAAAAAAAA"
"AAYFAgQHAQEBAQAAAAAAAAAAAAAAAAACAQQQAAECBQAHBQkAAAAAAAAAAAECAwAREhMEITFhoSIF"
"FUFR0UIGgZHBMlIjM1MWEQABAwQDAQEAAAAAAAAAAAABABECIWESA1ETIyIE/9oADAMBAAIRAxEA"
"PwBvl5SWEkkylpJMGsj1XjXSE1kCQuJ8Iy9W5DoxradFa6VDf8IJZAQ6loNtBooTJaqp3DP5oBlV"
"nWrTpEouQS/Cf4PO0uKbqWHGXTSlztSvuVFiZjmfLH3GUuMkzSoTMu8aiNsXet5/17hFyo6PR64V"
"ZnuqfqDDDySFpNpYH3E6aFjzGBr2DkMuFBSFDsWkilUdLftW13pWpcdWqnbBzI/l6hVXKZlROUSe"
"L1KX5zvAPXESjdHsTFWpxLKOJ54hIA1DZCj+Vx/3r96fCNrkvRaT0+V3zV/llplr9sVeHZui/ONk"
"H3dzt6cL/9k="
""/>";
;

void showDigital(Client client)
{
  Serial.println("digital");
  sendHeader(client,"Multi-page example-Digital");
  client.println("<h2>Digital Pins</h2>");
  // show the value of digital pins
  client.println("<table border='1'>");
  for (int i = 2; i < 8; i++) {
    pinMode(i, INPUT);
    digitalWrite(i, HIGH); // turn on pull-ups
    client.print("<tr><td>digital pin ");
    client.print(i);
    client.print(" </td><td>");
    if(digitalRead(i) == LOW)
      printP(client, led_off);
    else
      printP(client, led_on);
    client.println("</td></tr>");
  }
  client.println("</table>");

  client.println("</body></html>");
}


void showPrivate(Client client, TextFinder finder, boolean isPost)
{
  Serial.println("private");
  if(isPost)
  {
    Serial.println("isPost");
    finder.find("

"); // skip to the body
    // find parameters starting with "pin" and stop on the first blank line
    Serial.println("searching for parms");
    while(finder.findUntil("pinD", "

")){
      int pin = finder.getValue();       // the pin number
      int val = finder.getValue();       // 0 or 1
      Serial.print(pin);
      Serial.print("=");
      Serial.println(val);
      pinMode(pin, OUTPUT);
      digitalWrite(pin, val);
    }
  }
  sendHeader(client,"Multi-page example-Private");
  // table with buttons from 2 through 9
  // 2 to 5 are inputs, the other buttons are outputs

  client.println("<table border='1'>");

  // show the input pins
  for (int i = 2; i < 6; i++) {  // pins 2-5 are inputs
    pinMode(i, INPUT);
    digitalWrite(i, HIGH); // turn on pull-ups
    client.print("<tr><td>digital input ");
    client.print(i);
    client.print(" </td><td>");

    client.print("&nbsp </td><td>");
    client.print(" </td><td>");
    client.print("&nbsp </td><td>");


    if(digitalRead(i) == LOW)
      //client.print("Low");
      printP(client, led_off);
    else
      //client.print("high");
    printP(client, led_on);
    client.println("</td></tr>");
  }

  // show output pins 6-9
  // note pins 10-13 are used by the ethernet shield 
  for (int i = 6; i < 10; i++) {
    client.print("<tr><td>digital output ");
    client.print(i);
    client.print(" </td><td>");
    htmlButton(client, "On", "pinD", i, "1");
    client.print(" </td><td>");
    client.print(" </td><td>");
    htmlButton(client, "Off", "pinD", i, "0");
    client.print(" </td><td>");

    if(digitalRead(i) == LOW)
      //client.print("Low");
      printP(client, led_off);
    else
      //client.print("high");
    printP(client, led_on);
    client.println("</td></tr>");
  }
  client.println("</table>");
}

// create an HTML button
void htmlButton( Client client, char * label, char *name, int nameId, char *value)
{
  P(buttonBegin) = "<form action='/private' method='POST'><p><input type='hidden'
name='";
  printP(client, buttonBegin);
  client.print(name);
  client.print(nameId);
  client.print("' value='");
  client.print(value);
  P(buttonType) = "'><input type='submit' value='";
  printP(client, buttonType);
  client.print(label);
  P(buttonEnd) = "'/></form>";
  printP(client, buttonEnd);
}

void unknownPage(Client client, char *page)
{
  Serial.print("Unknown : ");
  Serial.println("page");

  sendHeader(client,"Unknown Page");
  client.println("<h1>Unknown Page</h1>");
  client.println(page);
  client.println("</body></html>");
}

void sendHeader(Client client, char *title)
{
  // send a standard http response header
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.print("<html><head><title>");
  client.println(title);
  client.println("</title><body>");
}

void printP(Client client, const prog_uchar *str)
{
  // copy data out of program memory into local storage, write out in
  // chunks of 32 bytes to avoid extra short TCP/IP packets
  // from webduino library Copyright 2009 Ben Combee, Ran Talbott
  uint8_t buffer[32];
  size_t bufferEnd = 0;

  while (buffer[bufferEnd++] = pgm_read_byte(str++))
  {
    if (bufferEnd == 32)
    {
      client.write(buffer, 32);
      bufferEnd = 0;
    }
  }

  // write out everything left but trailing NUL
  if (bufferEnd > 1)
    client.write(buffer, bufferEnd - 1);
}

Discussion

The logic used to create the web page is similar to that used in the previous recipes. The form here is based on Recipe 15.10, but it has more elements in the table and uses embedded graphical objects to represent the state of the pins. If you have ever created a web page, you may be familiar with the use of JPEG images within the page. Arduino does not have the capability to store images as .jpg files.

Images need to be encoded using one of the Internet standards such as Multipurpose Internet Mail Extensions (MIME). This provides a way to represent graphical (or other) media using text. The sketch in this recipe’s Solution shows what the LED images look like when they are MIME-encoded. Many web-based services will MIME-encode your images; the ones in this recipe were created using the service at http://www.motobit.com/util/base64-decoder-encoder.asp.

Even the small LED images used in this example are too large to fit into Arduino RAM. Program memory (flash) is used; see Recipe 17.3 for an explanation of the P(name) expression.

The images representing the LED on and off states are stored in a sequence of characters; the LED on array begins like this:

P(led_on) =  "<img src="data:image/jpg;base64,"

P(led_on) = defines led_on as the name of this array. The characters are the HTML tags identifying an image followed by the MIME-encoded data comprising the image.

This example is based on code produced for the Webduino web server. Webduino is highly recommended for building web pages if your application is more complicated than the examples shown in this chapter.

See Also

Webduino web page: http://code.google.com/p/webduino/

15.12. Sending Twitter Messages

Problem

You want Arduino to send messages to Twitter; for example, when a sensor detects some activity that you want to monitor via Twitter.

Solution

This sketch sends a Twitter message when a switch is closed. It uses the Twitter library developed by neocat, which you can download from http://www.arduino.cc/playground/Code/TwitterLibrary:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <Twitter.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 177 };
byte gateway[] = { 192, 168, 1, 254 };
byte subnet[] = { 255, 255, 255, 0 };

Twitter twitter("YourID:Password");

boolean MsgSent = false;
const int Sensor = 2;

void setup()
{
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);

  pinMode(Sensor, INPUT);
  digitalWrite(Sensor, HIGH);  //turn on  pull-up resistors
  delay(1000);
}

void loop()
{
  if(digitalRead(Sensor) == LOW)
  { // here if mailbox is open
    if(MsgSent == false){ // check if message already sent
       MsgSent = sendMessage("Mail has been delivered");
    }
  }
  else{
      MsgSent = false;  // door closed so reset the state
  }
  delay(100);
}

boolean sendMessage( char *message)
{
boolean isSent = false;

  Serial.println("connecting ...");
  if (twitter.post(message)) {
    int status = twitter.wait();
    if (status == 200) {
      Serial.println("OK.");
      isSent = true;
    } else {
      Serial.print("failed : code ");
      Serial.println(status);
    }
  } else {
    Serial.println("connection failed.");
  }
  delay(100);
  return isSent;
}

Discussion

The Twitter interface is encapsulated in the Twitter library. This posts to Twitter through an intermediary, http://arduino-tweet.appspot.com/, which spares you the problem of keeping up with changes to Twitter, most notably its authentication.

Twitter twitter("YourID:Password"); initializes the Twitter library; you will need to substitute your ID and password in that string in order to log on to Twitter.

twitter.post(message); attempts to send the message string “Mail has been delivered” to Twitter and returns true if it is able to connect.

twitter.wait(); gets the resultant status. A value of 200 (200 is the Internet standard reply for success) prints OK; otherwise, the error code is printed. See the documentation for the Twitter library (or the Twitter API documentation) for more details on the error codes.

The following version uses the same sendMessage function but can monitor an array of sensors:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <Twitter.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 177 };
byte gateway[] = { 192, 168, 1, 254 };
byte subnet[] = { 255, 255, 255, 0 };

Twitter twitter("YourID:Password");

char frontOpen[] = "The front door was opened";
char backOpen[] = "The back door was opened";

const int frontSensor = 2;
const int backSensor  = 3;

boolean frontMsgSent = false;
boolean backMsgSent = false;

void setup()
{
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);

  pinMode(frontSensor, INPUT);
  pinMode(backSensor, INPUT);
  digitalWrite(frontSensor, HIGH);  // pull-ups
  digitalWrite(backSensor, HIGH);

  delay(1000);
}

void loop()
{
  if(digitalRead(frontSensor) == LOW)
  { // here if door is open
    if(frontMsgSent == false){ // check if message already  sent
       frontMsgSent = sendMessage(frontOpen);
    }
  }
  else{
      frontMsgSent = false;  // door closed so reset the state
  }
  if(digitalRead(backSensor) == LOW)
  {
    if(frontMsgSent == false) {
       backMsgSent = sendMessage(backOpen);
    }
  }
  else {
      backMsgSent = false;
  }
  delay(100);
}
// add in the sendMessage function from the sketch above

The code that communicates with Twitter is the same, but the message string here is constructed from the values read from sensors connected to two Arduino digital pins.

15.13. Sending and Receiving Simple Messages (UDP)

Problem

You want to send and receive simple messages over the Internet.

Solution

This sketch uses the Arduino UDP (User Datagram Protocol) library to send and receive strings. In this simple example, Arduino prints the received string to the Serial Monitor and a string is sent back to the sender saying “acknowledged”:

*
 * UDPSendReceiveStrings
 * This sketch receives UDP message strings, prints them to the serial port
 * and sends an "acknowledge" string back to the sender
 *
*/

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <Udp.h> // UDP library from: [email protected] 12/30/2008


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address to use
byte ip[] = {192, 168, 1, 177 };    // Arduino's IP address

unsigned int localPort = 8888;      // local port to listen on

// the next two variables are set when a packet is received
byte remoteIp[4];        // holds received packet's originating IP
unsigned int remotePort; // holds received packet's originating port

// buffers for receiving and sending data
byte packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,
char replyBuffer[] = "acknowledged";       // a string to send back


void setup() {
    // start the Ethernet and UDP:
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);

  Serial.begin(9600);
}

void loop() {
  // if there's data available, read a packet
  int packetSize = Udp.available(); // note that this includes the UDP header
  if(packetSize)
  {
    packetSize = packetSize - 8;      // subtract the 8 byte header
    Serial.print("Received packet of size ");
    Serial.println(packetSize);

    // read packet into packetBuffer and get sender's IP addr and port number
    Udp.readPacket(packetBuffer,UDP_TX_PACKET_MAX_SIZE, remoteIp, &remotePort);
    Serial.println("Contents:");
    Serial.println((char*)packetBuffer);

    // send a string back to the sender
    Udp.sendPacket((byte*)replyBuffer,strlen(replyBuffer),remoteIp,remotePort);

  }
  delay(10);
}

You can test this by running the following Processing sketch on your computer (see Chapter 4 for guidance on installing and running Processing):

// Processing UDP example to send and receive string data from Arduino
// press any key to send the "Hello Arduino" message


import hypermedia.net.*;

UDP udp;  // define the UDP object


void setup() {
  udp = new UDP( this, 6000 );  // create a new datagram connection on port 6000
  //udp.log( true );            // <-- print out the connection activity
  udp.listen( true );           // and wait for incoming message
}

void draw()
{
}

void keyPressed() {
 String ip       = "192.168.1.177";     // the remote IP address
 int port        = 8888;      // the destination port

 udp.send("Hello World", ip, port );    // the message to send

}

void receive( byte[] data ) {           // <-- default handler
//void receive( byte[] data, String ip, int port ) {    // <-- extended handler

 for(int i=0; i < data.length; i++)
     print(char(data[i]));
 println();
}

Discussion

Plug the Ethernet shield into Arduino and connect the Ethernet cable to your computer. Upload the Arduino sketch and run the Processing sketch on your computer. Hit any key to send the “hello Arduino” message. Arduino sends back “acknowledged”, which is displayed in the Processing text window. String length is limited by a constant set in the Udp.h library file; the default value is 24 bytes, but you can increase this by editing the following line in Udp.h if you want to send longer strings:

#define UDP_TX_PACKET_MAX_SIZE 24

UDP is a simple and fast way to send and receive messages over Ethernet. But it does have limitations—the messages are not guaranteed to be delivered, and on a very busy network some messages could get lost or get delivered in a different order than that in which they were sent. But UDP works well for things such as displaying the status of Arduino sensors—each message contains the current sensor value to display, and any lost messages get replaced by messages that follow.

This sketch demonstrates sending and receiving sensor messages. It receives messages containing values to be written to the analog output ports and replies back to the sender with the values on the analog input pins:

/*
 * UDPSendReceive sketch:
*/
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <Udp.h> // UDP library from: [email protected] 12/30/2008

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC address to use
byte ip[] = {192, 168, 1, 177 };    // Arduino's IP address
byte gw[] = {192, 168, 1, 254 };    // Gateway IP address
unsigned int localPort = 8888;      // local port to listen on

byte packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,
int packetSize; // holds received packet size
byte remoteIp[4]; // holds received packet's originating IP
unsigned int remotePort; // holds received packet's originating port

const int analogOutPins[] = { 3,5,6,9};  // pins 10 and 11 used by ethernet shield

void setup() {
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);
  Serial.begin(9600);
  Serial.println("Ready");
}

void loop() {
  // if there's data available, read a packet
  packetSize = Udp.available();
  if(packetSize > 0 )
  {
    packetSize = packetSize - 8; // 8 byte UDP header so subtract 8 bytes
    Serial.print("Received packet of size ");
    Serial.print(packetSize);
    Serial.println(" with contents:");
    // read packet into packetBuffer and get sender's IP addr and port number
    packetSize = min(packetSize,UDP_TX_PACKET_MAX_SIZE);
    Udp.readPacket(packetBuffer,UDP_TX_PACKET_MAX_SIZE, remoteIp, &remotePort);

    for( int i=0; i < packetSize; i++)
    {
        byte value = packetBuffer[i];
        if( i < 4)
        {
           // only write to the first four analog out pins
           analogWrite(analogOutPins[i], value);
        }
        Serial.println(value, DEC);
    }
    Serial.println();
    // tell the sender the values of our analog ports
    sendAnalogValues(remoteIp, remotePort);
  }
  //wait a bit
  delay(10);
}

void sendAnalogValues( byte targetIp[], int targetPort )
{
  int index = 0;
  for(int i=0; i < 6; i++)
  {
     int value = analogRead(i);

     packetBuffer[index++] = lowByte(value);  // the low byte);
     packetBuffer[index++] = highByte(value); // the high byte);   }
  }
  //send a packet to specified peer
  Udp.sendPacket(packetBuffer, index, targetIp, targetPort);  
}

The sketch sends and receives the values on analog ports 0 through 5 using binary data. If you are not familiar with messages containing binary data, see the introduction to Chapter 4, as well as Recipes 4.6 and 4.7, for a detailed discussion on how this is done on Arduino.

The difference here is that the data is sent using UdpSendPacket instead of Serial.print.

Here is a Processing sketch you can use with the preceding sketch. It has six scroll bars that can be dragged with a mouse to set the six analogWrite levels; it prints the received sensor data to the Processing text window:

/*
 * UDPTest
 *
 * Demo to send and receive data from Arduino using UDP
 */

import hypermedia.net.*;

UDP udp;  // define the UDP object

HScrollbar[] scroll = new HScrollbar[6];  //see: topics/gui/scrollbar

void setup() {
  size(256, 200);
  noStroke();
  for(int i=0; i < 6; i++) // create the scroll bars
    scroll[i] = new HScrollbar(0, 10 + (height / 6) * i, width, 10, 3*5+1);

  udp = new UDP( this, 6000 );  // create a new datagram connection on port 6000
  //udp.log( true );            // <-- print out the connection activity
  udp.listen( true );           // and wait for incoming message
}

void draw()
{
  background(255);
  fill(255);
  for(int i=0; i < 6; i++) {
    scroll[i].update();
    scroll[i].display();
  }
}

void keyPressed() {
 String ip       = "192.168.1.177";    // the remote IP address
 int port        = 8888;               // the destination port

  byte[] message = new byte[6] ;
  for(int i=0; i < 6; i++){
    message[i] = byte(scroll[i].getPos());
    println(int(message[i]) );
  }
  println();
  udp.send( message, ip, port );

}


void receive( byte[] data ) {                           // <-- default handler
//void receive( byte[] data, String ip, int port ) {    // <-- extended handler

  println("incoming data is:");
  for(int i=0; i < 6; i++){
     scroll[i].setPos(data[i]);
     println((int)data[i]);
  }
}

class HScrollbar
{
  int swidth, sheight;    // width and height of bar
  int xpos, ypos;         // x and y position of bar
  float spos, newspos;    // x position of slider
  int sposMin, sposMax;   // max and min values of slider
  int loose;              // how loose/heavy
  boolean over;           // is the mouse over the slider?
  boolean locked;
  float ratio;

  HScrollbar (int xp, int yp, int sw, int sh, int l) {
    swidth = sw;
    sheight = sh;
    int widthtoheight = sw - sh;
    ratio = (float)sw / (float)widthtoheight;
    xpos = xp;
    ypos = yp-sheight/2;
    spos = xpos + swidth/2 - sheight/2;
    newspos = spos;
    sposMin = xpos;
    sposMax = xpos + swidth - sheight;
    loose = l;
  }

  void update() {
    if(over()) {
      over = true;
    } else {
      over = false;
    }
    if(mousePressed && over) {
      locked = true;
    }
    if(!mousePressed) {
      locked = false;
    }
    if(locked) {
      newspos = constrain(mouseX-sheight/2, sposMin, sposMax);
    }
    if(abs(newspos - spos) > 1) {
      spos = spos + (newspos-spos)/loose;
    }
  }

  int constrain(int val, int minv, int maxv) {
    return min(max(val, minv), maxv);
  }

  boolean over() {
    if(mouseX > xpos && mouseX < xpos+swidth &&
    mouseY > ypos && mouseY < ypos+sheight) {
      return true;
    } else {
      return false;
    }
  }

  void display() {
    fill(255);
    rect(xpos, ypos, swidth, sheight);
    if(over || locked) {
      fill(153, 102, 0);
    } else {
      fill(102, 102, 102);
    }
    rect(spos, ypos, sheight, sheight);
  }

  float getPos() {
    return spos * ratio;
  }

  void setPos(int value) {
    spos = value / ratio;
  }
}

15.14. Getting the Time from an Internet Time Server

Problem

You want to get the current time from an Internet time server; for example, to synchronize clock software running on Arduino.

Solution

This sketch gets the time from a Network Time Protocol (NTP) server and prints the results as seconds since January 1, 1900 (NTP time) and seconds since January 1, 1970:

/*
 * UdpNtp sketch
 *
 * Get the time from an NTP time server
 * Demonstrates use of UDP sendPacket and ReceivePacket
 */

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <Udp.h>


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  // MAC address to use
byte ip[] = { 192, 168, 1, 44 };                      // Arduino's IP address

unsigned int localPort = 8888;      // local port to listen for UDP packets

byte time_dot_nist_dot_gov[] = { 192, 43, 244, 18}; // time.nist.gov NTP server

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the
message

byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming/outgoing packets

void setup()
{
  // start Ethernet and UDP
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);

  Serial.begin(9600);
}

void loop()
{
  sendNTPpacket(time_dot_nist_dot_gov); // send an NTP packet to a time server
  // wait to see if a reply is available
  delay(1000);
  if ( Udp.available() ) {
     Udp.readPacket(packetBuffer,NTP_PACKET_SIZE);  // read packet into buffer

     //the timestamp starts at byte 40, convert four bytes into a long integer
     unsigned long hi = word(packetBuffer[40], packetBuffer[41]);
     unsigned long low = word(packetBuffer[42], packetBuffer[43]);
     unsigned long secsSince1900 = hi << 16 | low;  // this is NTP time (seconds
since Jan 1 1900)

    Serial.print("Seconds since Jan 1 1900 = " );
    Serial.println(secsSince1900);

    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970
    const unsigned long seventyYears = 2208988800UL;     
    unsigned long epoch = secsSince1900 - seventyYears;  // subtract 70 years
    Serial.println(epoch);                               // print Unix time


    // print the hour, minute and second:
    // UTC is the time at Greenwich Meridian (GMT)
    Serial.print("The UTC time is ");       
    // print the hour (86400 equals secs per day)
    Serial.print((epoch  % 86400L) / 3600); 
    Serial.print(':'),
    // print the minute (3600 equals secs per minute)
    Serial.print((epoch  % 3600) / 60); 
    Serial.print(':'),
    Serial.println(epoch %60); // print the second
  }
  // wait ten seconds before asking for the time again
  delay(10000);
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(byte *address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);  // set all bytes in the buffer to 0

  // Initialize values needed to form NTP request
  packetBuffer[0] = B11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum
  packetBuffer[2] = 6;     // Max Interval between messages in seconds
  packetBuffer[3] = 0xEC;  // Clock Precision
  // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
  packetBuffer[12]  = 49;  // four byte reference ID identifying
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // send the packet requesting a timestamp:
  Udp.sendPacket( packetBuffer,NTP_PACKET_SIZE,  address, 123); //NTP requests
are to port 123
}

Discussion

NTP is a protocol used to synchronize time through Internet messages. NTP servers provide time as a value of the number of seconds that have elapsed since January 1, 1900. NTP time is UTC (Coordinated Universal Time, similar to Greenwich Mean Time) and does not take time zones or daylight saving time into account.

NTP servers use UDP messages; see Recipe 15.13 for an introduction to UDP. An NTP message is constructed in the function named sendNTPpacket and you are unlikely to need to change the code in that function. The function takes the address of an NTP server; you can use the IP address in the preceding example or find a list of many more using “NTP address” as a search term in Google. If you want more information about the purpose of the NTP fields, see the documentation at http://www.ntp.org/.

The reply from NTP is a message with a fixed format; the time information consists of four bytes starting at byte 40. These four bytes are a 32-bit value (an unsigned long integer), which is the number of seconds since January 1, 1900. This value (and the time converted into Unix time) is printed. If you want to convert the time from an NTP server to the friendlier format using hours, minutes, and seconds and days, months, and years, you can use the Arduino Time library (see Chapter 12). Here is a variation on the preceding code that prints the time as 14:32:56 Monday 18 Jan 2010:

/*
 * Time_NTP sketch
 * Example showing time sync to NTP time source
 *
 * This sketch uses the Time library
 * and the Arduino Ethernet library
 */

#include <Time.h>
#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Udp.h>
#include <Ethernet.h>


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 44 };

unsigned int localPort = 8888;      // local port to listen for UDP packets

byte time_dot_nist_dot_gov[] = { 192, 43, 244, 18}; // time.nist.gov

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in first 48 bytes of message
byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming/outgoing packets

time_t prevDisplay = 0;    // when the digital clock was displayed

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  while(timeStatus()== timeNotSet)
     ; // wait until the time is set by the sync provider
}

void loop()
{
  if( now() != prevDisplay)   //update the display only if the time has changed
  {
    prevDisplay = now();
    digitalClockDisplay();
  }
}

void digitalClockDisplay(){
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(dayStr(weekday()));
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(monthShortStr(month()));
  Serial.print(" ");
  Serial.print(year());
  Serial.println();
}

void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and
leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0'),
  Serial.print(digits);
}

/*-------- NTP code ----------*/

unsigned long getNtpTime()
{
  sendNTPpacket(time_dot_nist_dot_gov);
  delay(1000);
  if ( Udp.available() ) {
     Udp.readPacket(packetBuffer,NTP_PACKET_SIZE);  // read packet into the buffer

     //the timestamp starts at byte 40, convert four bytes into a long integer
     unsigned long hi = word(packetBuffer[40], packetBuffer[41]);
     unsigned long low = word(packetBuffer[42], packetBuffer[43]);
     // this is NTP time (seconds since Jan 1 1900
     unsigned long secsSince1900 = hi << 16 | low;  
     // Unix time starts on Jan 1 1970
     const unsigned long seventyYears = 2208988800UL;     
     unsigned long epoch = secsSince1900 - seventyYears;  // subtract 70 years
     return epoch;
  }
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(byte *address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE);  // set all bytes in the buffer to 0

  // Initialize values needed to form NTP request
  packetBuffer[0] = B11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum
  packetBuffer[2] = 6;     // Max Interval between messages in seconds
  packetBuffer[3] = 0xEC;  // Clock Precision
  // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
  packetBuffer[12]  = 49;  // four-byte reference ID identifying
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // send the packet requesting a timestamp:
  // NTP requests are to port 123
  Udp.sendPacket( packetBuffer,NTP_PACKET_SIZE,  address, 123); 
}

See Also

Chapter 12 provides more information on using the Arduino Time library.

Details on NTP are available at http://www.ntp.org/.

NTP code by Jesse Jaggars that inspired the sketch used in this recipe is available at http://github.com/cynshard/arduino-ntp.

15.15. Monitoring Pachube Feeds

Problem

You want Arduino to respond to information on a web service that offers security and data backup. Perhaps you want to activate a device or raise an alarm based on the value of data on Pachube.

Solution

This sketch gets the first four data fields from feed number 504 and prints the results on the Serial Monitor:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include <TextFinder.h>

const int  feedID           =    504; // this is the ID of the
remote Pachube feed that you want to connect to
const int  streamCount      =      4; // Number of data streams to get
const long REFRESH_INTERVAL = 600000; // Update every 10 minutes
const long RETRY_INTERVAL   = 10000;  // if connection fails/resets
wait 10 seconds before trying again - should not be less than 5

#define PACHUBE_API_KEY  "your key here . . ." // fill in your API key

// mac address, make sure this is unique on your network
byte mac[] = { 0xCC, 0xAC, 0xBE, 0xEF, 0xFE, 0x91 }; 
byte remoteServer[] = { 209,40,205,190 };            // pachube.com

byte ip[] = { 192, 168, 1, 144  };                   // no DHCP so set IP address
byte gateway[] ={ 192, 168, 1, 254 };
byte subnet[] ={ 255, 255, 255, 0 };

float streamData[streamCount];    // change float to long if needed for your data
char findBuffer     [11];  // holds one numeric field - room for 10 characters
and the terminating null

Client client(remoteServer, 80);
TextFinder  finder( client );

void setup()
{
  Serial.begin(9600);
  Ethernet.begin(mac, ip, gateway, subnet);
}

void loop()
{
   if( getFeed(feedID, streamCount) == true)
   {
      for(int id = 0; id < streamCount; id++){
        Serial.println( streamData[id]);
      }
      Serial.println("--");
      delay(REFRESH_INTERVAL);
   }
   else
   {
      Serial.println("Unable to get feed");
      delay(RETRY_INTERVAL);
   }
}

// returns true if able to connect and get data for all requested streams
in this feed
boolean getFeed(int feedId, int streamCount )
{
boolean result = false;
  Serial.print("Connecting feed "); Serial.print(feedId); Serial.print(" ... ");
  if (client.connect()) {
    client.print("GET /api/feeds/");
    client.print(feedId);
   // client.print(".csv HTTP/1.1
Host: pachube.com
X-PachubeApiKey: ");
    client.print(".xml HTTP/1.1
Host: pachube.com
X-PachubeApiKey: ");
    client.print(PACHUBE_API_KEY);
    client.print("
User-Agent: Arduino");
    client.println("
");
  }
  else {
    Serial.println("Connection failed");
  }
  if (client.connected()) {
    Serial.println("Connected");
    if(  finder.find("HTTP/1.1") && finder.find("200 OK") )
       result = processFeed(streamCount);
    else
       Serial.println("Dropping connection - no 200 OK");
  }
  else {
    Serial.println("Disconnected");
  }
  client.stop();
  client.flush();
  return result;
}

int processFeed(int streamCount)
{
  finder.find("<environment updated=");
  finder.getString("T", """,findBuffer, sizeof(findBuffer));    // get the time
  Serial.print("Values updated at ");
  Serial.println(findBuffer);

  int processed = 0;
  for(int id = 0; id < streamCount; id++)
  {
    if( finder.find( "<data id=" ) )  //find next data field
    {
       if(finder.find("<value ")  )
       {
         finder.find(">"); // seek to the angle brackets
         streamData[id] = finder.getValue();
         processed++;
       }
    }
    else {
      Serial.print("unable to find Id field ");
      Serial.println(id);
    }
  }
  return(processed == streamCount );  // return true iff got all data
}

Discussion

To start using Pachube, you have to sign up for an account, and the Pachube Quickstart page explains how: http://community.pachube.com/?q=node/4. Once you’re signed up, you will be emailed a username and API key. Add your key to the following line in the sketch:

#define PACHUBE_API_KEY  "your key here . . ." // fill in your API key

Every Pachube feed (data source) has an identifying ID; this example sketch uses feed 504 (environmental data from the Pachube office). Feeds are accessed using the getFeed method with the feed ID and the number of items of data to get passed as arguments. If this is successful, getFeed returns true, and you can process the data using the processFeed method. This returns the value for the data item you are interested in (each data item is called a stream in Pachube).

15.16. Sending Information to Pachube

Problem

You want Arduino to update feeds on Pachube. For example, you want the values of sensors connected to Arduino to be published on a Pachube feed.

Solution

This sketch reads temperature sensors connected to the analog input ports (see Recipe 6.8) and sends the data to Pachube:

#if ARDUINO > 18
#include <SPI.h>         // needed for Arduino versions later than 0018
#endif

#include <Ethernet.h>
#include "Dhcp.h"  // uses DHCP code from: http://blog.jordanterrell.com
#include <TextFinder.h>

const int  feedID           =    2955; // this is the ID of my float test feed
const int  streamCount      =      6;  // Number of data streams to send
const long REFRESH_INTERVAL = 60000;   // Update every  minute
// if connection fails/resets wait 10 seconds before trying again
// should not be less than 5
const long RETRY_INTERVAL   = 10000;  

#define PACHUBE_API_KEY  "Your key here . . . " // fill in your API key

// make sure this is unique on your network
byte mac[] = { 0xCC, 0xAC, 0xBE, 0xEF, 0xFE, 0x91 }; 
byte remoteServer[] = { 209,40,205,190 };            // pachube.com

//used to store csv output and response strings (date is 26 chars)
char buffer[32];            

Client      client(remoteServer, 80);
TextFinder  finder( client );

void setup()
{
  Serial.begin(9600);
  Serial.println("Getting ip address");
  if(Dhcp.beginWithDHCP(mac) == 1) {  // begin method returns 1 if successful
    delay(4000);
  }
  else {
    Serial.println("unable to acquire ip address!");
    while(true)
      ;  // do nothing
  }
}

void loop()
{

int data[streamCount];
  buffer[0] = 0;
  char * bufPointer = buffer;
  for(int id = 0; id < streamCount; id++)
  {
     float temperature = getTemperature(id)   ;
     formatFloat( temperature, 1, &buffer[strlen(buffer)]);  // format as xx.y
     if( id < streamCount-1)
        strcat(buffer, ",");                // commas between all but last value
  }
  if( putFeed(feedID, streamCount) == true)
  {
     Serial.print("Feed updated: ");
     Serial.println(buffer);
     delay(REFRESH_INTERVAL);
  }
  else
  {
      Serial.println("Unable to update feed");
      delay(RETRY_INTERVAL);
  }
}

// returns true if able to connect and send data
boolean putFeed(int feedId, int streamCount )
{
boolean result = false;
  Serial.print("Connecting feed "); Serial.print(feedId); Serial.print(" ... ");
  if (client.connect()) {
    client.print("PUT /api/feeds/");
    client.print(feedId);
    client.print(".csv HTTP/1.1
Host: pachube.com
X-PachubeApiKey: ");
    //client.print(".xml HTTP/1.1
Host: pachube.com
X-PachubeApiKey: ");
    client.print(PACHUBE_API_KEY);
    client.print("
User-Agent: Arduino");
    client.print("
Content-Type: text/csv
Content-Length: ");
    client.print(strlen(buffer));
    client.print("
Connection: close

");
    client.print(buffer);

    client.println("
");
  }
  else {
    Serial.println("Connection failed");
  }
  if (client.connected()) {
    Serial.println("Connected");
    if(  finder.find("HTTP/1.1") && finder.find("200 OK") ){
       finder.getString("Date: ", "
",buffer, sizeof(buffer));  // store time
       result = true;
    }
    else
       Serial.println("Dropping connection - no 200 OK");
  }
  else {
    Serial.println("Disconnected");
  }
  client.stop();
  client.flush();
  return result;
}

float getTemperature(int pin)
{
   const int nbrSamples = 8; // take the average of 8 samples
   int samples = 0;
   for(int i=0; i < nbrSamples; i++)
       samples = samples + analogRead(pin);
   float t = samples / nbrSamples; // get the average value
   t =  (t /1024.0) * 500;  // each degree C is 10mv
   return t;
}

// returns the number of characters added to the buffer
int formatFloat(float number, byte digits, char *buf)
{
  if (!buf )
    return 0;
  char * bufStart = buf;  // store the start of the buffer
  // Handle negative numbers
  if (number < 0.0)
  {
     *buf++ = '-';
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;

  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  ultoa(int_part, buf, 10 );
  buf = &buf[strlen(buf)];

  // Print the decimal point, but only if there are digits
  if (digits > 0 )
    *buf++ = '.';

  // Extract digits from the remainder one at a time
  while (digits-- > 0 )
  {
    remainder *= 10.0;
    int toPrint = int(remainder);
    *buf++ = toPrint + '0';
    remainder -= toPrint;
  }
  *buf = 0;
  return buf-bufStart; // the number of characters added
}

Discussion

This is similar to Recipe 15.15, but here you use the putFeed method to send your information to Pachube. This example sends information from temperature sensors, and almost half the code is used to get and format this information in a form suitable for display. See the chapter covering the type of sensor you want to use to find code suitable for your application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.25.32