Skip to content

Instantly share code, notes, and snippets.

@lancefisher
Created January 2, 2012 07:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lancefisher/1549667 to your computer and use it in GitHub Desktop.
Save lancefisher/1549667 to your computer and use it in GitHub Desktop.
Netduino Webserver that sets the nixie tubes for Twilio Rube Goldberg Contest
using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
namespace NetduinoArduinix
{
public class ArduinixShield
{
/// <summary>
/// There are two SN74141 chips on the Arduinix. These Netduino Output ports are connected
/// to the inputs of the first SN74141.
/// </summary>
/// <remarks>
/// SN74141 Truth Table
/// D C B A #
/// L,L,L,L 0
/// L,L,L,H 1
/// L,L,H,L 2
/// L,L,H,H 3
/// L,H,L,L 4
/// L,H,L,H 5
/// L,H,H,L 6
/// L,H,H,H 7
/// H,L,L,L 8
/// H,L,L,H 9
/// </remarks>
public static OutputPort[] SN74141AInputs =
{
new OutputPort(Pins.GPIO_PIN_D2, false), //A
new OutputPort(Pins.GPIO_PIN_D3, false), //B
new OutputPort(Pins.GPIO_PIN_D4, false), //C
new OutputPort(Pins.GPIO_PIN_D5, false) //D
};
/// <summary>
/// There are two SN74141 chips on the Arduinix. These Netduino Output ports are connected
/// to the inputs of the second SN74141.
/// </summary>
public static OutputPort[] SN74141BInputs =
{
new OutputPort(Pins.GPIO_PIN_D6, false), //A
new OutputPort(Pins.GPIO_PIN_D7, false), //B
new OutputPort(Pins.GPIO_PIN_D8, false), //C
new OutputPort(Pins.GPIO_PIN_D9, false) //D
};
/// <summary>
/// Writing to these Netduino OutputPorts fires the high voltage anode on the Arduinix Shield
/// </summary>
public static OutputPort[] Anodes =
{
new OutputPort(Pins.GPIO_PIN_D10, false),
new OutputPort(Pins.GPIO_PIN_D11, false),
new OutputPort(Pins.GPIO_PIN_D12, false),
//todo: support the 4th anode for larger displays
};
/// <summary>
/// Displays the numbers on the nixie tubes. Designed for 6 tube display.
/// Each integer should be &lt; 16. 0-9 will show on the display. 11-15 will blank that digit.
/// Integers greater than 15 start over at 0. e.g. 16 displays 0, 17 displays 1, etc.
/// </summary>
public void DisplayNumbers(int number1, int number2, int number3, int number4, int number5, int number6)
{
//I'm not sure if my anodes are wired to my display in the standard way. -Lance
DisplaySet(Anodes[1], number1, number4);
DisplaySet(Anodes[0], number5, number2);
DisplaySet(Anodes[2], number3, number6);
}
/// <summary>
/// Displays the number on the arduinix display.
/// </summary>
/// <param name="number">
/// The number to display. For a six-digit display,
/// the number should be less than 999,999.
/// </param>
/// <param name="displayMode">
/// The number can be padded with zeros to the left,
/// or aligned to the right or left with leading zeros blanked.
/// </param>
public void DisplayNumber(int number, DisplayMode displayMode = DisplayMode.ZeroPadded)
{
//Get each digit of the number, up to 6 digits
int[] digits =
{
number / 100000 % 10,
number / 10000 % 10,
number / 1000 % 10,
number / 100 % 10,
number / 10 % 10,
number % 10
};
if (displayMode == DisplayMode.AlignRight)
{
//blank leading zeros
for (var i = 0; i < digits.Length; i++)
{
if (digits[i] != 0) break;
digits[i] = 10; //setting to 10 will blank that digit
}
}
if (displayMode == DisplayMode.AlignLeft)
{
var leadingZeroCount = 0;
for (var i = 0; i < digits.Length; i++)
{
if (digits[i] != 0) break;
leadingZeroCount++;
}
//move the digits left
for (var i = 0; i < digits.Length - leadingZeroCount; i++)
{
digits[i] = digits[i + leadingZeroCount];
}
//blank the old positions
for (var i = digits.Length - leadingZeroCount; i < digits.Length; i++)
{
digits[i] = 10;
}
}
DisplayNumbers(
digits[0],
digits[1],
digits[2],
digits[3],
digits[4],
digits[5]
);
}
public enum DisplayMode
{
ZeroPadded,
AlignRight,
AlignLeft
}
//digits, anode, chip
//1, 2, 1
//2, 1, 1
//3, 3, 1
//4, 2, 2
//5, 1, 2
//6, 3, 2
private static void DisplaySet(OutputPort anode, int number1, int number2)
{
var input1 = GetInput(number1);
WriteInput(input1, SN74141AInputs);
var input2 = GetInput(number2);
WriteInput(input2, SN74141BInputs);
anode.Write(true);
Thread.Sleep(2);
anode.Write(false);
}
private static bool[] GetInput(int number)
{
//Get an array of bool that represents the binary version of number...
var b = new[]
{
(number & 1) != 0,
(number & 2) != 0,
(number & 4) != 0,
(number & 8) != 0,
};
return b;
}
private static void WriteInput(bool[] input, OutputPort[] inputs)
{
for (var i = 0; i < input.Length; i++)
inputs[i].Write(input[i]);
}
}
}
using System;
using Microsoft.SPOT;
//thanks: http://forums.netduino.com/index.php?/topic/575-updated-web-server/
// This is needed for extension methods to work
namespace System.Runtime.CompilerServices
{
public class ExtensionAttribute : Attribute { }
}
namespace NetduinoPlusWebServer
{
/// <summary>
/// Extension methods
/// </summary>
public static class Extension
{
/// <summary>
/// Replace characters in a string
/// </summary>
/// <param name="stringToSearch"></param>
/// <param name="charToFind"></param>
/// <param name="charToSubstitute"></param>
/// <returns></returns>
public static string Replace(this string stringToSearch, char charToFind, char charToSubstitute)
{
// Surely there must be nicer way than this?
char[] chars = stringToSearch.ToCharArray();
for (int i = 0; i < chars.Length; i++)
if (chars[i] == charToFind) chars[i] = charToSubstitute;
return new string(chars);
}
}
}
using System;
using Microsoft.SPOT;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
//thanks: http://forums.netduino.com/index.php?/topic/575-updated-web-server/
namespace NetduinoPlusWebServer
{
public delegate void RequestReceivedDelegate(Request request);
public class Listener : IDisposable
{
const int maxRequestSize = 1024;
readonly int portNumber = 80;
private Socket listeningSocket = null;
private RequestReceivedDelegate requestReceived;
public Listener(RequestReceivedDelegate RequestReceived)
: this(RequestReceived, 80) { }
public Listener(RequestReceivedDelegate RequestReceived, int PortNumber)
{
portNumber = PortNumber;
requestReceived = RequestReceived;
listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listeningSocket.Bind(new IPEndPoint(IPAddress.Any, portNumber));
listeningSocket.Listen(10);
new Thread(StartListening).Start();
}
~Listener()
{
Dispose();
}
public void StartListening()
{
while (true)
{
using (Socket clientSocket = listeningSocket.Accept())
{
IPEndPoint clientIP = clientSocket.RemoteEndPoint as IPEndPoint;
Debug.Print("Received request from " + clientIP.ToString());
var x = clientSocket.RemoteEndPoint;
int availableBytes = clientSocket.Available;
Debug.Print(DateTime.Now.ToString() + " " + availableBytes.ToString() + " request bytes available");
int bytesReceived = (availableBytes > maxRequestSize ? maxRequestSize : availableBytes);
if (bytesReceived > 0)
{
byte[] buffer = new byte[bytesReceived]; // Buffer probably should be larger than this.
int readByteCount = clientSocket.Receive(buffer, bytesReceived, SocketFlags.None);
using (Request r = new Request(clientSocket, Encoding.UTF8.GetChars(buffer)))
{
Debug.Print(DateTime.Now.ToString() + " " + r.URL);
if (requestReceived != null) requestReceived(r);
}
}
}
// I always like to have this in a continuous loop. Helps prevent lock-ups
Thread.Sleep(10);
}
}
#region IDisposable Members
public void Dispose()
{
if (listeningSocket != null) listeningSocket.Close();
}
#endregion
}
}
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using NetduinoArduinix;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO;
namespace NetduinoPlusWebServer
{
public class Program
{
const string WebFolder = "\\SD\\Web";
static readonly object Locker = new object();
static int _numbers = -1;
static readonly ArduinixShield Arduinix = new ArduinixShield();
public static void Main()
{
var webServer = new Listener(RequestReceived);
while (true)
{
if (_numbers == -1)
//blank the display
Arduinix.DisplayNumbers(10, 10, 10, 10, 10, 10);
else
Arduinix.DisplayNumber(_numbers);
}
}
private static void RequestReceived(Request request)
{
// Use this for a really basic check that it's working
//request.SendResponse("<html><body><p>Request from " + request.Client.ToString() + " received at " + DateTime.Now.ToString() + "</p><p>Method: " + request.Method + "<br />URL: " + request.URL +"</p></body></html>");
//watch for /favicon.ico
Debug.Print("URL: " + request.URL);
//request.
if (request.URL == "/")
{
const string html = @"<!DOCTYPE html>
<html>
<head><title>Nixie Tubes</title></head>
<body>
<h1>Set the nixie tubes!</h1>
<form>
<div style='margin-bottom:15px'><label>Numbers: <input id=numbers type=text></label></div>
<div><input type=submit value='Set the nixie tubes!'></div>
</form>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js'></script>
<script>
$(function() {
$('input[type=submit]').click(function(){
$.get('/numbers/' + $('#numbers').val());
return false;
});
});
</script>
</body>
</html>
";
request.SendResponse(html);
return;
}
if (request.URL.Length < 9 || request.URL.Substring(0, 9) != "/numbers/")
{
Debug.Print("URL not matched");
Debug.Print(request.URL.Substring(0, 8));
return;
}
var newNumbers = int.Parse(request.URL.Trim('/').Split('/')[1]);
lock(Locker)
{
_numbers = newNumbers;
}
Debug.Print("numbers: " + _numbers);
request.SendResponse("{ 'ok' : true, 'numbers': " + _numbers + "}");
// Send a file
//TrySendFile(request);
}
/// <summary>
/// Look for a file on the SD card and send it back if it exists
/// </summary>
/// <param name="request"></param>
private static void TrySendFile(Request request)
{
// Replace / with \
string filePath = WebFolder + request.URL.Replace('/', '\\');
if (File.Exists(filePath))
request.SendFile(filePath);
else
request.Send404();
}
}
}
using System;
using Microsoft.SPOT;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.IO;
//thanks: http://forums.netduino.com/index.php?/topic/575-updated-web-server/
namespace NetduinoPlusWebServer
{
/// <summary>
/// Holds information about a web request
/// </summary>
/// <remarks>
/// Will expand as required, but stay simple until needed.
/// </remarks>
public class Request : IDisposable
{
private string method;
private string url;
private Socket client;
const int fileBufferSize = 256;
internal Request(Socket Client, char[] Data)
{
client = Client;
ProcessRequest(Data);
}
/// <summary>
/// Request method
/// </summary>
public string Method
{
get { return method; }
}
/// <summary>
/// URL of request
/// </summary>
public string URL
{
get { return url; }
}
/// <summary>
/// Client IP address
/// </summary>
public IPAddress Client
{
get
{
IPEndPoint ip = client.RemoteEndPoint as IPEndPoint;
if (ip != null) return ip.Address;
return null;
}
}
/// <summary>
/// Send a response back to the client
/// </summary>
/// <param name="response"></param>
public void SendResponse(string response, string type = "text/html")
{
if (client != null)
{
string header = "HTTP/1.0 200 OK\r\nContent-Type: " + type + "; charset=utf-8\r\nContent-Length: " + response.Length.ToString() + "\r\nConnection: close\r\n\r\n";
client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
client.Send(Encoding.UTF8.GetBytes(response), response.Length, SocketFlags.None);
Debug.Print("Response of " + response.Length.ToString() + " sent.");
}
}
/// <summary>
/// Sends a file back to the client
/// </summary>
/// <remarks>
/// Assumes the application using this has checked whether it exists
/// </remarks>
/// <param name="filePath"></param>
public void SendFile(string filePath)
{
// Map the file extension to a mime type
string type = "";
int dot = filePath.LastIndexOf('.');
if (dot != 0)
switch (filePath.Substring(dot + 1))
{
case "css":
type = "text/css";
break;
case "xml":
case "xsl":
type = "text/xml";
break;
case "jpg":
case "jpeg":
type = "image/jpeg";
break;
case "gif":
type = "image/gif";
break;
// Not exhaustive. Extend this list as required.
}
using (FileStream inputStream = new FileStream(filePath, FileMode.Open))
{
// Send the header
string header = "HTTP/1.0 200 OK\r\nContent-Type: " + type + "; charset=utf-8\r\nContent-Length: " + inputStream.Length.ToString() + "\r\nConnection: close\r\n\r\n";
client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
byte[] readBuffer = new byte[fileBufferSize];
while (true)
{
// Send the file a few bytes at a time
int bytesRead = inputStream.Read(readBuffer, 0, readBuffer.Length);
if (bytesRead == 0)
break;
client.Send(readBuffer, bytesRead, SocketFlags.None);
Debug.Print("Sending " + readBuffer.Length.ToString() + "bytes...");
}
}
Debug.Print("Sent file " + filePath);
}
/// <summary>
/// Send a Not Found response
/// </summary>
public void Send404()
{
string header = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
if (client != null)
client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
Debug.Print("Sent 404 Not Found");
}
/// <summary>
/// Process the request header
/// </summary>
/// <param name="data"></param>
private void ProcessRequest(char[] data)
{
string content = new string(data);
string firstLine = content.Substring(0, content.IndexOf('\n'));
// Parse the first line of the request: "GET /path/ HTTP/1.1"
string[] words = firstLine.Split(' ');
method = words[0];
url = words[1];
// Could look for any further headers in other lines of the request if required (e.g. User-Agent, Cookie)
}
#region IDisposable Members
public void Dispose()
{
if (client != null)
{
client.Close();
client = null;
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment