Goal:
To turn a Netduino Plus into a web server capable of handling images and a small amount of preprocessing.
Background:
There are quite a few people who have turned their Netduino into a web server:
http://netduinohacking.blogspot.com/2011/03/netduino-plus-web-server-hello-world.html
http://neonmikawebserver.codeplex.com/
http://embeddedwebserver.codeplex.com/
Looked like a lot of fun, so I decided to experiment with it myself. What I wanted in a web server was something that was able to handle fairly complex websites, but still light weight. I feel that I was able to strike that balance with this web server.
Creating the Web Server:
So, first things first, let’s create a socket for the server to listen on for connections:
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind(new IPEndPoint(IPAddress.Any, port));
_socket.Listen(Backlog);
The above code creates a new socket and then binds that socket to a port. As far as the port goes, you just need to find an open one. On the netduino, this should be easy, because all of them should be open and available to be used (0 – 65535). If you are doing prototyping on a PC prior to deploying it to the netduino (highly recommended), finding an open port is a little more challenging. Usually ports larger than 1024 are open with ports larger than 49151 having an even higher probability of being open.
I always like doing any blocking socket calls on another thread, so that application has a chance to do something else while it is waiting, if it wants to:
_thread = new Thread(new ThreadStart(ListenForClients));
_thread.Start();
So, it is pretty easy to create a new thread and point it to a function to execute when it runs (ListenForClients).
Putting it together gives us a constructor that looks something like this:
public class WebServer : IDisposable
{
private const int Backlog = 10;
private Socket _socket = null;
private Thread _thread = null;
private string _location = null;
public WebServer(string location, int port = 80)
{
_location = location;
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind(new IPEndPoint(IPAddress.Any, port));
_socket.Listen(Backlog);
_thread = new Thread(new ThreadStart(ListenForClients));
_thread.Start();
}
...
}
Now, I haven’t mentioned the location argument yet, because we haven’t needed it yet, but we will. It is basically the location of the web pages on the micro SD that we are going to be returning to anyone who requests them.
Listening For Connections:
Now we need to listen for clients to connect to our web server and request web pages:
private void ListenForClients()
{
while (true)
{
using (Socket client = _socket.Accept())
{
// Wait for data to become available
while (!client.Poll(10, SelectMode.SelectRead)) ;
int bytesSent = client.Available;
if (bytesSent > 0)
{
byte[] buffer = new byte[bytesSent];
int bytesReceived = client.Receive(buffer, bytesSent, SocketFlags.None);
if (bytesReceived == bytesSent)
{
string request = new string(Encoding.UTF8.GetChars(buffer));
Debug.Print(request);
Respond(client, request);
}
}
}
}
}
First, we tell the socket to accept an incoming connection. This is a blocking call. By that I mean that the program will stop there on that line and wait until someone comes and connects to the socket. Once the client connects to the socket, we then wait for the client to request something before we move on. That’s what the while loop does where we poll the client. (Polling is basically asking repeated, and in this case we keep asking whether or not data is available until we get the answer that we want. Much like a kid wanting candy, except that we ask every 10 microseconds.)
Once there is data available, then we allocate a buffer to store the request in and receive the data that the client sends to us. HTTP requests are strings that are encoded into bytes using the UTF8 standard. So, we have to decode the byte array that we are sent and recover the string that contains the client’s request. We then print out the request, for debugging purposes and then respond to the client’s request.
Responding To Requests:
A typical HTTP request looks something like this:
GET /index.html HTTP/1.1
Host: 192.168.1.150:1554
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.49 Safari/537.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
DNT: 1
The part that we are most concerned about is the file that is being requested (index.html). The rest of it gives more details about the requestor so that the response can be tailored for them. For this web server, we aren’t going to worry about this, we are just going to return the file that they want. In order to do this, we need to do some parsing to determine which file they want:
string filename = StringHelper.GetTextBetween(request, "GET", "HTTP");
public static class StringHelper
{
public static string GetTextBetween(string str, string a, string b)
{
if (str == null || str == String.Empty) { return String.Empty; }
int aIdx = str.IndexOf(a);
if (aIdx == -1) { return String.Empty; }
int strt = aIdx + a.Length;
int bIdx = str.IndexOf(b, strt);
if (bIdx == -1) { return String.Empty; }
int stop = bIdx;
return str.Substring(strt, stop - strt).Trim();
}
...
}
Nothing too fancy here, we just determine the location of “GET” and “HTTP” strings within the request and then we extract the characters between them, trimming off any whitespace.
Once we have the requested file, we send the requested file to the user:
private const int FileBufferLength = 1024;
private const string ResponseBegin = "HTTP/1.0 200 OK\r\nContent-Type: text; charset=utf-8\r\nContent-Length: ";
private const string ResponseEnd = "\r\nConnection: close\r\n\r\n";
private void SendFile(Socket client, string file)
{
if (File.Exists(file))
{
using (FileStream stream = File.Open(file, FileMode.Open))
{
long fileSize = stream.Length;
string header = ResponseBegin + fileSize.ToString() + ResponseEnd;
client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
byte[] buffer = new byte[FileBufferLength];
while (stream.Position < stream.Length)
{
int xferSize = System.Math.Min(FileBufferLength, (int)(stream.Length - stream.Position));
stream.Read(buffer, 0, xferSize);
client.Send(buffer, xferSize, SocketFlags.None);
}
}
}
else
{
SendErrorResponse(client);
}
}
Before we send the file that the client is requesting, we need to send them some information about what they are about to get. Here is a typical response:
HTTP/1.0 200 OK
Content-Type: text; charset=utf-8
Content Length: 1024
Connection: close
The major piece of information that we need to tell the client is the length of our response, in bytes (1024). The rest of it is hard coded in a response string, and then the response length is inserted into the middle of it.
After we tell the client how much data we are going to be sending them, we go about reading the data from the SD card and then sending it to the client in small manageable chunks. The small chunks are so that we don’t overload the netduino’s buffer and lose data.
Error Checking:
There is also some error checking in there to make sure that the file that the client wants is a file that we have. If it is not, then we send them back an error response. There are a couple of options on how to do this. We could send an error message back in the initial response, telling the browser that the file was not found, or we could tell the browser that everything is fine and send back a webpage to the client that informs them that the file could not be found. I’m going to do the latter because it makes for a prettier web page when something goes wrong. So, here’s the code that sends back an error web page when the requested file could not be found:
private static byte[] ErrorResponse = Encoding.UTF8.GetBytes("<HTML><HEAD><TITLE>Website</TITLE></HEAD><BODY><H1>Content Not Found</H1></BODY></HTML>");
private void SendErrorResponse(Socket client)
{
string header = ResponseBegin + ErrorResponse.Length.ToString() + ResponseEnd;
client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
client.Send(ErrorResponse, ErrorResponse.Length, SocketFlags.None);
}
We are still using the same header, but we are returning a webpage that simply states “Content Not Found”. Which should look something like this:
Cleaning Up:
Finally, we need to do some clean up when the web server class is no longer needed. If you remember back from the definition of the class, it said that it was going to implement the IDisposable interface, which is the standard way to clean up resources when you are done with them. Here’s the code:
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_socket.Close();
_thread.Abort();
}
_disposed = true;
}
}
~WebServer()
{
Dispose(false);
}
#endregion
This piece of code closes the socket and aborts the thread when we are done with the web server.
Main Program:
Now all we need to do is start up our web server and wait for clients to connect:
public class Program
{
private const string WebsiteFilePath = @"\SD\";
public static void Main()
{
using (WebServer server = new WebServer(WebsiteFilePath, 1554))
{
while (true)
{
}
}
}
}
After we create the web server, we go into an infinite loop. This is because since the web server runs on a separate thread, the call to create the web server will return almost immediately. Since, we don’t have anything that we need to do, we just loop forever.
The port number choice is arbitrary. I picked 1554 after the New Belgium beer.
Adding a Temperature Sensor:
All of the web page content that we have been talking about so far has been static content. Meaning that all the webserver does is grab a file from the SD card and hand it to the user. I wanted to go one small step further and incorporate some dynamic content to the website. By this I mean that I wanted to do some preprocessing on the files that I retrieve from the SD card, before I send them to the client. (Think PHP.)
A common netduino project seems to be home automation and common task within that is to take a temperature measurement. So, I thought it would be neat to add some preprocessing code into the html file that tells the netduino to read the current temperature in the room and embed that in the html response.
So, here is the circuit that I wired up to the A0 pin of the netduino:
Then I wrote the following class that takes a temperature measurement:
public class MCP9701E
{
private AnalogInput _input = null;
private const float Vdd = 3.3f;
private const int TempSensorMin = 0;
private const int TempSensorMax = 1023;
private const float V0 = 0.4f;
private const float Tc = 0.0195f;
public MCP9701E(Cpu.Pin pin)
{
_input = new AnalogInput(pin);
_input.SetRange(TempSensorMin, TempSensorMax);
}
public float GetTemperature()
{
int measurements = 5;
long sum = 0;
for (int i = 0; i < measurements; i++)
{
sum += _input.Read();
Thread.Sleep(100);
}
sum = sum / measurements;
float voltage = ((float)sum) / (TempSensorMax - TempSensorMin + 1) * Vdd;
return (voltage - V0) / Tc;
}
}
The conversion from voltage to temperature is right out of the data sheet. The only interesting part of this code is that I take 5 measurements, spaced 100 milliseconds apart, and averages the results. This yielded a more consistent temperature measurement.
Integrating the Temperature Sensor:
To tie it all together, I decided to create my own file type (a .sme file), and define a html tag (<?sme … ?>) that told my preprocessor that the text between it needed to be interpreted. By creating my own file type, only files of that type have to be read in and checked for the magic tag that I created.
In order to keep it simple, my preprocessor language only has one command ($a0), which reads the value from A0 analog input pin and returns its current value. This was done in the interest of keeping the code simple and straight-forward. Hopefully in the future I’ll expand on this as I need more features, but this is good enough for now.
So, here is what the html code looks like for my sample page:
<html>
<title>Netduino</title>
<body>
<h1 style="text-align: center;">Netduino Web Server!</h1>
<h2 style="text-align: center;">Current Temperature: <?sme $a0 ?> °C</h2>
<img style="display: block; margin-right: auto; margin-left: auto;" src="house.jpg" alt="house"></img>
</body>
</html>
And this is what it looks like when viewed in the browser:
Summary:
In this post I described creating a web server using a netduino plus and an sd card. The web server was capable of handling text and images, along with some minimal preprocessing. I also demonstrated how to use a temperature sensor with the netduino and report the current temperature in the server room.