Of Pork, Freezers, Tweets, and Node.js
Monday, January 24, 2011It was summer and I was craving pork. Not just any pork, but Tails and Trotters pork -- fed with hazelnuts and absolutely delicious. I somehow convinced my lovely partner-in-crime to split the cost of half of a pig; there was only one problem, we had a freezer but its pedigree was entirely unknown. Rather than take a chance on losing a whole lot of yummy a plan was hatched: we'd bring the freezer into the 21st century (or at least the monitoring of it).
The first pass was built with a combination of technologies. The biggest part was built on top of an Arduino, with a thermistor to get a temperature. A quick sketch was written to poll for the temperature and data was output via USB to a Linux server running a daemon written in Perl. Communication with the outside world was pretty straightforward: listen for data, send out updates to Twitter via twurl, and respond to any outliers. It was nothing fancy, but it gave us peace of mind enough to take a vacation out of state.
It was cool, but I wasn't quite satisfied. For one, it didn't have easy access to historical data; for two there was no way to get instant feedback. It was time to go (pardon the pun) whole hog.
The second pass was a little more involved: again the Arduino, but this time with the addition of an ethernet shield. The idea was to have the Arduino communicate directly to an external web service that could collect data and have the ability to communicate that data with the outside world both with Twitter and directly via HTTP.
Different technologies were chosen this time: Node.js to allow connection to the outside world, Redis for collection of data, Google's visualization API for display of the data, and socket.io to deliver the data real-time.
Step one was to write a quick sketch to gather the data and report it off to the Node.js server:
#include <SPI.h> #include <Ethernet.h> #include <math.h> byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD }; byte ip[] = { 192, 168, 1, 45 }; byte gateway[] = { 192, 168, 1, 1 }; byte subnet[] = { 255, 255, 255, 0 }; byte server[] = { 204, 128, 215, 10 }; int ledPin = 13; // LED connected to digital pin 13 int tempPin = 5; // thermister read pin // set up the client request Client client(server, 8080); // returns the celsius value from the resistence reading // math is neat double convertToCelsius(int RawADC) { double temp; temp = log(((10240000/RawADC) - 10000)); temp = 1 / (0.001129148 + (0.000234125 * temp) + (0.0000000876741 * temp * temp * temp)); temp = temp - 273.15; return temp; } void setup() { Ethernet.begin(mac, ip, gateway, subnet); delay(1000); pinMode(ledPin, OUTPUT); } void loop() { // start of the run, to keep near 5 seconds unsigned long start = millis(); static int pinStatus = 0; double temp = convertToCelsius(analogRead(tempPin)); if (client.connect()) { // make request client.print("GET /post?key=SHAREDKEY&temperature="); client.println(temp); client.println(); // ignore any response, disconnect client.stop(); } digitalWrite(ledPin, (++pinStatus % 2) ? HIGH : LOW); delay(5000 - (start - millis())); }
The second part was an interesting mix of client-side and server-side JavaScript. On the server side data collection happened via HTTP:
// create the base webserver, set up handlers for normal files and post update var server = http.createServer(function (request, response) { var parsed = url.parse(request.url, true); var pathname = parsed.pathname; if (pathname === '/post') { handlePost(request, response); } else { if (pathname === '/') { pathname = 'index.html'; } var file = utils.path(settings.htdocs, pathname); utils.handleFile(request, response, file); } });
Data gets gathered and stuffed away into a Redis database. Outliers continue to be handled in the same way, but moving the tweets directly into code allowed for some simplification and removed reliance on outside code.
function tweet(status) { oAuth = new OAuth("http://twitter.com/oauth/request_token", "http://twitter.com/oauth/access_token", settings.twitterConsumerKey, settings.twitterConsumerSecret, "1.0A", null, "HMAC-SHA1"); oAuth.post("http://api.twitter.com/1/statuses/update.json", settings.twitterAccessToken, settings.twitterAccessTokenSecret, { "status": status }, function (error, data) { if (error) { console.log("Failed twitter update.\n" + require('sys').inspect(error)); } else { console.log("update complete"); } }); }
The fun comes from the web interface. Now that everything has been moved to be a web service, getting data back is simple. A websockets connection via socket.io allows for any update from the Arduino to be broadcast to any listening client. Throw in some graphs, and the beginnings of a great application are born.
See it in action: here.