For my final project in Sensitive Buildings I worked with Lynn Burke, Noah Crowley, Margaret McKenna, and Doug Thistlewaite. We were drawn together because we all had an interest in working with many different types of sensors to log and visualize data.
Create a wireless sensor network using XBees to measure the temperature, humidity, air quality, and noise at various locations throughout the building at 240 Central Park South and elsewhere. Log and visualize the sensor data in real-time. Create an interface that allows a user to control a small electronic appliance, like a lamp or air conditioner, remotely (or from the convenience of their own bed).
We all did a lot of experimentation with various sensors. In the end, we had to rein ourselves in a little bit, and focus on the core metrics that we wanted to capture. For temperature sensing we selected the TMP36 because of it's ease of use. Honeywell's HIH-4030 was chosen for humidity, specically in breakout board form from Sparkfun. Doug spent some time with Eric Rosenthal perfecting the final sensor board circuit as seen below.
I also experimented with a Sparkfun BOB-09964 Electret Microphone Breakout and Sharp's GP2Y1010AU0F Optical Dust Sensor.
The sensor data comes to our server via a ConnectPort X2 Gateway running XIG. XIG, short for XBee Internet Gateway, is written and maintained by Rob Faludi (our professor), Jordan Husney, and Ted Hayes. XIG has a mode where it will send any I/O samples it recieves to a remote URL using an HTTP GET. Using XIG was super easy, and it allowed us to focus our attention on other aspects of our project, like logging the data and visualizing it.
We chose a DIY approach with regard to logging the sensor data because, as great as Pachube is (and other related services are), there is something to be said for owning your own data, and for having control over your own codebase. Basically, doing it ourselves meant we would not be beholden to a service that may or may not be around in the future, and which we have no direct control over.
I experimented heavily with TCP sockets, and felt strongly that we should build our solution around sockets. Sockets would allow us to exchange data in close to realtime. I did some early tests using a new version of Guiseppe that I've been working on for the past few months. Guiseppe is an application made for bridging serial and socket communication. I also prototyped a couple of things in Flash, because it has powerful built-in socket classes, and I still think it's a fantastic prototyping platform (I don't care what anyone else says). These experiments, while fun, weren't really going anywhere.
Noah setup an Amazon EC2 instance for us to start experimenting on. And Margaret started working with WebSockets, an emerging W3C standard. Specifically she built an admin interface for managing sensors, and a real-time sensor display using node.js. It was at this point that things really started to come together for us.
Doug then used the sensor data collected over a weekend to produce a heatmap of the areas we were collecting data in.
My Flash experiments allowed me to control an LED attached to an XBee from my iPad. However, I really wanted to experiment with Python and jQuery Mobile. I had basically no experience with Python prior to this class, and jQuery Mobile is simply the new hotness. I also wanted to play around with my < href="http://www.sparkfun.com/products/9875">Sparkfun RFID Starter Kit.
The lamp control page didn't need to be anything too fancy. But I thought it would be a good idea to reflect the current state of the lamp (on or off) when the page first loads. And obviously, it needed to control the lamp. For this I turned to iDigi. Digi's Developer Cloud has a Remote Command Interface (RCI) that allows you to send queries and commands to a specific XBee from across the internet, provided the radio is connected to a ConnectPort Gateway. The RCI commands are sent as HTTP POSTs formatted as XML. iDigi has tools for crafting these commands in several programming languages.
The lamp control page consists of an HTML / jQuery Mobile frontend, as well as a PHP backend which provides the interface with iDigi. Here is the HTML and JavaScript source of the lamp control page:
<!DOCTYPE html>
<html>
<head>
<title>Sensitive Buildings</title>
<link rel="apple-touch-icon-precomposed" href="icon.png"/>
<link rel="apple-touch-startup-image" href="startup.png">
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script>
<script type="text/javascript">
var xmlPath = "lightOnOff.php";
var lampState;
$(document).ready(function() {
$.ajax({
type: "GET",
url: xmlPath,
dataType: "xml",
success: function(xml) {
lampState = $(xml).find('dio3_config').text();
if (String(lampState) == "5") {
$('label.on > span > span.ui-icon').removeClass('ui-icon-radio-off').addClass('ui-icon-radio-on');
} else {
$('label.off > span > span.ui-icon').removeClass('ui-icon-radio-off').addClass('ui-icon-radio-on');
}
}
});
});
function toggleLampState(state) {
$.ajax({
type: "GET",
url: xmlPath+"?state="+state,
dataType: "xml",
success: function(xml) {
alert("State changed.");
}
});
}
</script>
<style type="text/css">
.clear {
clear: both;
}
.spacer {
height: 20px;
}
.header {
margin-left: 20px;
}
</style>
</head>
<body>
<div data-role="page" data-theme="e" data-content-theme="e">
<div data-role="header">
<p class="header">Sensitive Buildings</p>
</div><!-- /header -->
<div data-role="content">
<div id="radios" data-role="fieldcontain" class="clear">
<fieldset data-role="controlgroup">
<legend>Lamp Control:</legend>
<input type="radio" name="radio-choice-1" id="radio-choice-1" value="choice-1" onclick="toggleLampState('5')" />
<label class="on" for="radio-choice-1">On</label>
<input type="radio" name="radio-choice-1" id="radio-choice-2" value="choice-2" onclick="toggleLampState('4')" />
<label class="off" for="radio-choice-2">Off</label>
</fieldset>
</div>
</div><!-- /content -->
</div><!-- /page -->
</body>
</html>
And here is the PHP source of the lamp control page:
<?php
// The id of your ConnectPort Gateway
$gateway = "00000000-00000000-00000000-00000000";
// The id of your XBee Radio
$xbee = "00:00:00:00:00:00:00:00!";
// Your iDigi login and password
$credentials = "login:password";
if ($_GET['mode'] == 'escape') {
$mode = true;
} else {
$mode = false;
}
if ($_GET['state'] == '4') {
$state = '4';
} else if ($_GET['state'] == '5') {
$state = '5';
} else {
$state = false;
}
if ($state != false) {
$xml_data = '<sci_request version="1.0"><send_message><targets><device id="'.$gateway.'"/></targets><rci_request version="1.1"><do_command target="zigbee"><set_setting addr="'.$xbee.'"><radio><dio3_config>'.$state.'</dio3_config></radio></set_setting></do_command></rci_request></send_message></sci_request>';
} else {
$xml_data = '<sci_request version="1.0"><send_message><targets><device id="'.$gateway.'"/></targets><rci_request version="1.1"><do_command target="zigbee"><query_setting addr="'.$xbee.'"/></do_command></rci_request></send_message></sci_request>';
}
$url = "http://developer.idigi.com/ws/sci";
$headers = array(
"Authorization: Basic " . base64_encode($credentials),
"Content-type: text/xml;charset=\"utf-8\"",
"Content-length: ".strlen($xml_data)
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, $defined_vars['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_data);
$data = curl_exec($ch);
if (curl_errno($ch)) {
print "Error: " . curl_error($ch);
} else {
if ($mode) {
echo htmlspecialchars($data);
} else {
echo $data;
}
curl_close($ch);
}
?>
But wait, there's more! Keeping in mind that I know very little about Python, here's a Python script that looks for RFID data coming over a serial port, and sends commands through iDigi to control the lamp as well:
import socket
import sys
import os
import time
import urllib
import httplib
import base64
username = "username" # Your iDigi username
password = "password" # Your iDigi password
# Also replace the device id and radio address in the XML below
# I know, I know, they really should be broken out as variables
# Don't forget to change the serial port toward the bottom of the script too
data = ""
state = "4"
def rfid():
print "rfid"
global state
max_bytes = 50
data = ""
try:
data = os.read(serfd, max_bytes)
print "read: "+data
except OSError, e:
print "no read"
finally:
if (data <> ""):
idigi()
if (state == "4"):
state = "5"
else:
state = "4"
time.sleep(2)
print "finally"
def idigi():
print "idigi"
global state
# create HTTP basic authentication string, this consists of
# "username:password" base64 encoded
auth = base64.encodestring("%s:%s"%(username,password))[:-1]
# message to send to server
message = """<sci_request version="1.0">
<send_message>
<targets>
<device id="00000000-00000000-00000000-00000000"/>
</targets>
<rci_request version="1.1">
<do_command target="zigbee">
<set_setting addr="00:00:00:00:00:00:00:00!">
<radio>
<dio3_config>"""+state+"""</dio3_config>
</radio>
</set_setting>
</do_command>
</rci_request>
</send_message>
</sci_request>
"""
webservice = httplib.HTTP("developer.idigi.com",80)
# to what URL to send the request with a given HTTP method
webservice.putrequest("POST", "/ws/sci")
# add the authorization string into the HTTP header
webservice.putheader("Authorization", "Basic %s"%auth)
webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
webservice.putheader("Content-length", "%d" % len(message))
webservice.endheaders()
webservice.send(message)
# get the response
statuscode, statusmessage, header = webservice.getreply()
response_body = webservice.getfile().read()
# print the output to standard out
print (statuscode, statusmessage)
print response_body
try:
# Replace with the serial port you want to monitor
serfd = os.open( '/dev/tty.usbserial-00000000', os.O_RDWR | os.O_NONBLOCK)
print "serial open"
finally:
while True:
rfid()
The lamp control circuit itself is explained in the video below. But basically it's an XBee attached to an Arduino which is in turn attached to a Power Switch Tail II. Many thanks to Noah for the tip on using the Power Switch Tail. I'll try to post a proper schematic at some point.
Video of me testing lamp actuation via the mobile web app and using RFID.
One of the tools that made working with the XBees on this final project much easier was Drone, a desktop application made with REAL Studio for capturing and analyzing XBee API frames. I started working on Drone mid-semester as a way of getting more comfortable with the API mode and it's data structures. Once you have a tool like this, that let's you see into the radio's brain so to speak, to see what it's saying and thinking, the data becomes more accessible and everything gets much easier. I hope to release Drone to the world sometime early next year once I've had a chance to polish it up a bit.
Video of Drone, software for managing XBee API Frames.
› fatbits