WiServer
From AsyncLabs Documentation
This page provides an introductory overview of WiServer and how to use it. For detailed technical documentation, please refer to the WiServer Build Instructions and the WiServer API Reference.
If you have further questions about WiServer or have an issue that you'd like us to look into, please post in the WiServer section of the Async Labs forums.
Contents |
Introduction
WiServer is an easy-to-use HTTP library developed by Mark Patel specifically for use with the WiShield. It enables a WiShield-equipped Arduino to act as a web server and/or a web client while requiring minimal code in the sketch. It's key features include:
- Server mode support for incoming HTTP 1.0 GET requests
- Client mode support for outgoing HTTP 1.0 POST and GET requests, plus a dedicated API for sending Twitter status updates
- Progressive content transmission for sending content larger than the stack buffer
- Support for multiple simultaneous connections and sharing of connections between client and server requests
- Full integration with the uIP stack including support for packet retransmission and connection time outs
- On-demand content generation for low memory usage
While WiServer has been designed to use very little RAM, an Arduino Duemilanove with 2k of RAM is recommended to ensure reliable operation. However, we will continue to optimize the code for both size and RAM usage in the hopes of enabling usage with lower-powered processors.
Callback Functions
WiServer is tightly integrated with the uIP TCP/IP stack used by the WiShield, and it embraces uIP's philosophy of coordinating the stack and application activities to greatly reduce memory usage. Instead of storing web pages and other data in memory prior to transmission, the sketch provides callback functions that generate content on-the-fly by directly writing to the TCP packet buffer. While this approach may seem a little awkward at first, it's actually very easy to code for and has several advantages on a highly-constrained device such as an Arduino board. WiServer's APIs have been designed to hide all the underlying details and make sketch writing as simple and painless as possible.
Generating Content
Like the Serial API, WiServer inherits from the Arduino Print class so it includes all of its methods for printing strings, characters and numbers; these methods are used by the sketch to generate web page content and the body of POST requests and Twitter messages. These functions only work in the context of a callback from WiServer (calling them at any other time does nothing).
In addition to the methods defined by the Print class, the WiServer also includes special methods for printing strings and data that are stored in program memory using the PROGMEM keyword. Strings stored in program memory have the advantage of not requiring any RAM, but they do require special treatment since they are stored in a different address space; to address this shortcoming, print_P and println_P methods are provided so that these strings can be printed just as easily as regular strings
Progressive Content Transmission
Despite its reliance on the uIP's diminutive buffer, WiServer can send content of any length. This ability is made possible through the use of a progressive transmission approach that asks the sketch to generate the same content several times so it can transmit several smaller blocks of the data in sequence. No special assistance from the sketch is needed to support this capability other then to generate the content repeatedly as requested. Using this mechanism, several kilobytes of data stored in program memory can be transmitted using a stack buffer of only a few hundred bytes.
For example, consider a sketch that writes a 500 byte web page and a buffer of 200 bytes. The first time the function is called, the first 200 bytes will be stored in the buffer and the remaining 300 bytes will be ignored. Once the first 200 byte packet has been successfully sent, the sketch will be asked to generate the same content again, but this time the next 200 bytes will be sent, and the first 200 bytes and last 100 bytes will be discarded. Once the second packet has been sent, the sketch will be asked to generate the page one more time and only the remaining 100 bytes will be transmitted.
Web Server Basics
WiServer makes it easy for a sketch to serve dynamically generated web pages as well as static content stored in program memory. To act as a web server, the sketch needs to implement a page serving function that the library can call when it has received a complete HTTP request.
Within this function, the sketch should check the required URL parameter to see if it is recognized, and then use the library's various print and println functions to generate the requested content. The function should return true if the URL was recognized, or false if it the URL is 'Not Found'; WiServer will automatically take care of sending the appropriate HTTP header based on the return value.
Here's an example of a simple page serving function that displays a 'Hello World' web page:
boolean sendMyPage(char* URL) { // Check if the URL matches "/" if (strcmp(URL, "/") == 0) { // Use print and println functions to write out the page WiServer.print("<html>"); WiServer.print("Hello World!"); WiServer.print("</html>"); // URL was recognized return true; } // URL not found return false; }
Note that the page serving function must have a single char* parameter for the URL and it must return a boolean value.
WiServer must be provided with the name of the page serving function when it is initialized in the sketch's setup() function. Simply pass the name of the function as the parameter value:
void setup() { WiServer.init(sendMyPage); }
Finally, WiServer's server_task() method must be called repeatedly from the sketch's loop function to drive the process:
void loop() { // // Do other stuff... // WiServer.server_task(); }
Web Client Basics
WiServer also supports client mode communications and can send HTTP 1.0 POST and GET requests. All client requests are queued so that uIP connections can be shared between client and/or server requests. Client requests are guaranteed to be processed in the order that they are submitted.
Client requests are made by creating a suitable request object with the desired parameters. and then submitting it. The same object can be re-used to make the same request multiple times, but it can cannot be resubmitted if it still in the queue or being serviced.
Authorization Strings
Some web sites such as Twitter require a user name and password for certain requests, and this information is provided in the form of an authorization string. The string is comprised of the user name, a colon, and the password, and it is encoded using Base64. An authorization string can be easily encoded in Base64 using a web-based conversion utility.
For example, if your user name is 'foo' and your password is 'bar', the raw authorization string would be foo:bar. Using a conversion utility, the same string with Base64 encoding is Zm9vOmJhcg==. So you would declare your authorization string as:
char auth[] = "Zm9vOmJhcg=="; // Base64 encoded USERNAME:PASSWORD
Note that the string must be stored as a regular string in memory (strings stored in program memory cannot be used).
Alternatively, you can use WiServer's new base64encode function to convert a raw authorization string from within your sketch:
char auth[] = WiServer.base64encode("foo:bar"); // Base64 encoded USERNAME:PASSWORD
Tweet Requests
The TWEETrequest object is actually a special version of the POSTrequest object that simplifies the process of sending Twitter updates. A TWEETrequest can be created using just two parameters: the authorizations string for the Twitter account, and the name of the function that will generate the message. Note that the 'status=' prefix is automatically added to the body of a TWEETrequest, so only the message itself needs to be provided by the function:
// Function that generates our tweet message void myTweet() { WiServer.print("This Tweet was brought to you via WiServer!"); }
// Request object for Tweeting using the myTweet function TWEETrequest sendTweet(auth, myTweet);
// Call the request's submit function to send the tweet sendTweet.submit();
If desired, you can create multiple request objects to send messages using different body functions. For example, here's some code that sends different tweets based on the analog value of pin 0:
void highMsg() { WiServer.print("Value on pin 0 is too high: "); WiServer.print(analogRead(0)); }
void lowMsg() { WiServer.print("Value on pin 0 is too low: "); WiServer.print(analogRead(0)); }
TWEETrequest highTweet(auth, highMsg);
TWEETrequest lowTweet(auth, lowMsg);
// Send twitter update if needed if (analogRead(0) > 512) { highTweet.submit(); } else if (analogRead(0) < 128) { lowTweet.submit(); }
GET Requests
WiServer can also handle generic GET requests. Create a GETrequest object using the server's IP address, the host name, and the URL of the request:
// IP Address for www.weather.gov uint8 ip[] = {140,90,113,200};
// Create a request that gets the latest METAR weather data for LAX GETrequest getWeather(ip, 80, "www.weather.gov", "/data/METAR/KLAX.1.txt");
To process data returned by the server, create a callback function and pass it to the request object:
// Function to handle data from the server void processData(char* data, int len) { // Do stuff with the data here }
// Have the processData function called when data is returned by the server getWeather.setReturnFunc(processData);
As with the TWEETrequest, call submit() to send the request:
getWeather.submit();
Note that the returned data includes the HTTP header as well as the requested content. If the data is larger than the uIP buffer, it will be broken up into several packets and the return function will be called for each one. The return function is also called once with a length value of 0 to indicate the end of the data.
POST Requests
POST requests are similar to GET requests, only they also include a content body that immediately follows the header. When creating a POSTrequest object, a body function must also be specified to provide the contents of the request body each time it is submitted.
// This function generates the body of our POST request void searchQuery() { WiServer.print("searchword=WiServer"); }
// IP Address uint8 ip[] = {64,50,161,160};
// Create a POST request using the searchQuery function to generate the body POSTrequest sendInfo(ip, 80, "www.asynclabs.com", "/index.php", searchQuery);
The body function can write any number of bytes, including 0. WiServer will automatically set the content length field in the HTTP header and will use progressive transmission if the data is larger than the uIP buffer.
Call submit() to send the request:
sendInfo.submit();