Networked physical computing projects face two major problems: finding each other on the Internet, and dealing with NAT filtering at Internet access routers.
Previously, I have dealt with this using a cheap shell account, where I ran a python server program at a known IP address to receive UDP packets sent by physical computing devices. The server program could then help the devices communicate by sending UDP packets back to the physical computing devices.
Unfortunately, I am finding that UDP, the unreliable datagram protocol, is becoming less and less reliable all the time. In particular, it looks like my cheap shell account system is rejecting small UDP packets with just a few characters of payload.
Given that the world is going to Web services, I figured hey, Google Apps are free (for low-CPU utilization use), so I went ahead and created a Google App for generalized physical computing networking.
The App keeps a database of devices and their states. For example, device "board1lLED" may have the state "1" to indicate an LED is on. If you hit the main "/" URL, you get a human-readable display of all the received devices and their states and an opportunity to enter your own device and state for an update.
Physical computing devices interface with the web service through the "/update" URL using a GET. The "d" parameter is a device to set to a state indicated by the "s" parameter, and a "q" parameter allows you to also query for the state of a device. For example GETing "/update?d=board1LED&s=1&q=button1" sets board1LED to 1 and gets the value of button1, which may return "button1:pressed" if its state is "pressed".
The App uses about 40 CPU mS per run, thus the 6.5 CPU hour/day quota should allow me to hit it 6 times per second all the time without running over the quota.
import cgi
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
class DeviceState(db.Model):
device = db.StringProperty()
device_state = db.StringProperty()
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
states=db.GqlQuery("SELECT * FROM DeviceState ORDER BY device")
for state in states:
self.response.out.write("<div>"+cgi.escape(state.device)+":"+
cgi.escape(state.device_state)+"</div>")
self.response.out.write("""
<form action="/humanSet" method="get">
<div>Device:<input type="text" name="d"></div>
<div>State:<input type="text" name="s"></div>
<div><input type="submit" value="submit"></div>
</form>
</body>
</html>""")
class UpdateState(webapp.RequestHandler):
def get(self):
if(self.request.get('d')):
states=db.GqlQuery("SELECT * FROM DeviceState WHERE device = :1",
self.request.get('d'))
if(states.count()>0):
for state in states:
state.device_state=self.request.get('s')
state.put()
else:
deviceState = DeviceState()
deviceState.device = self.request.get('d')
deviceState.device_state = self.request.get('s')
deviceState.put()
if(self.request.get('q')):
if(self.request.get('q')=='*'):
states=db.GqlQuery("SELECT * FROM DeviceState ORDER BY device")
else:
states=db.GqlQuery("SELECT * FROM DeviceState WHERE device='"+
self.request.get('q')+"'")
for state in states:
self.response.out.write(cgi.escape(state.device)+":"+
cgi.escape(state.device_state)+"\r\n")
class HumanSet(webapp.RequestHandler):
def get(self):
states=db.GqlQuery("SELECT * FROM DeviceState WHERE device = :1",
self.request.get('d'))
if(states.count()>0):
for state in states:
state.device_state=self.request.get('s')
state.put()
else:
deviceState = DeviceState()
deviceState.device = self.request.get('d')
deviceState.device_state = self.request.get('s')
deviceState.put()
self.redirect('/')
class Reset(webapp.RequestHandler):
def get(self):
states=DeviceState.all()
for state in states:
state.delete()
self.response.out.write("All Data Deleted\n")
application = webapp.WSGIApplication([('/',MainPage),
('/humanSet',HumanSet),
('/update',UpdateState),
('/reset',Reset)],debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()