diff service/bedroomArduino/bedroomArduino.py @ 834:a87cae710556

bedroomarduino project started. web server isn't ready Ignore-this: 892495965fde1ee17162505d50222f81 darcs-hash:20120418045319-312f9-fa3fc17f9d94881c257ebee128ca5d8623cb4d94.gz
author drewp <drewp@bigasterisk.com>
date Tue, 17 Apr 2012 21:53:19 -0700
parents
children 8e55a6a9c425
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/bedroomArduino/bedroomArduino.py	Tue Apr 17 21:53:19 2012 -0700
@@ -0,0 +1,146 @@
+#!bin/python
+"""
+talks to bed.pde on an arduino
+"""
+
+from __future__ import division
+
+import cyclone.web, json, traceback, os, sys, time, logging
+from twisted.internet import reactor, task
+from twisted.web.client import getPage
+sys.path.append("/my/proj/house/frontdoor")
+from loggingserial import LoggingSerial        
+sys.path.append("/my/proj/homeauto/lib")
+from cycloneerr import PrettyErrorHandler
+from logsetup import log
+
+sys.path.append("/my/site/magma")
+from stategraph import StateGraph      
+from rdflib import Namespace
+
+ROOM = Namespace("http://projects.bigasterisk.com/room/")
+DEV = Namespace("http://projects.bigasterisk.com/device/")
+
+class ArduinoBedroom(object):
+    def __init__(self, port='/dev/ttyACM0'):
+        self.ser = LoggingSerial(port=port, baudrate=115200, timeout=1)
+        self.ser.flush()
+
+    def ping(self):
+        self.ser.write("\x60\x00\x00")
+        msg = self.ser.readJson()
+        assert msg == {"ok":True}, msg
+
+    def poll(self):
+        self.ser.write("\x60\x01\x00")
+        ret = self.ser.readJson()
+        return ret
+
+    def setSpeakerChoice(self, pillow):
+        self.ser.write("\x60\x02" + chr(pillow))
+        return self.ser.readJson() 
+
+class Index(PrettyErrorHandler, cyclone.web.RequestHandler):
+    def get(self):
+        """
+        this is an acceptable status check since it makes a round-trip
+        to the arduino before returning success
+        """
+        self.settings.arduino.ping()
+        
+        self.set_header("Content-Type", "application/xhtml+xml")
+        self.write(open("index.html").read())
+
+class SpeakerChoice(PrettyErrorHandler, cyclone.web.RequestHandler):
+    def put(self):
+        ret = self.settings.arduino.setSpeakerChoice(int(self.get_argument('pillow')))
+        self.write(ret)
+
+class GraphPage(PrettyErrorHandler, cyclone.web.RequestHandler):
+    def get(self):
+        self.set_header("Content-Type", "application/x-trig")
+        g = StateGraph(ROOM['bedroomArduino'])
+        self.settings.poller.assertIsCurrent()
+        g.add((DEV['bedroomMotion'], ROOM['state'],
+               ROOM['motion'] if self.settings.poller.lastValues['motion'] else
+               ROOM['noMotion']))
+        self.write(g.asTrig())
+
+class Poller(object):
+    """
+    Watches sensor values
+    """
+    def __init__(self, config, ard, period):
+        self.config, self.ard = config, ard
+        self.period = period
+        self.lastValues = None
+        self.lastPollTime = 0
+        self.lastMotion = False
+
+    def assertIsCurrent(self):
+        """raise an error if the poll data is not fresh"""
+        dt = time.time() - self.lastPollTime
+        if dt > period * 2:
+            raise ValueError("last poll time was too old: %.1f sec ago" % dt)
+    
+    def poll(self):
+        now = time.time()
+        try:
+            try:
+                newData = ard.poll()
+            except ValueError, e:
+                print e
+            else:
+                print newData
+                return
+                self.lastPollTime = now
+                self.lastValues = newData # for other data besides the blinks
+                self.processMotion(newData['motion'])
+            
+        except (IOError, OSError):
+            os.abort()
+        except Exception, e:
+            print "poll error", e
+            traceback.print_exc()
+
+    def processMotion(self, state):
+        if state == self.lastMotion:
+            return
+        self.lastMotion = state
+        msg = json.dumps(dict(board=self.config['boardName'], 
+                              name="bedroomMotion", state=state))
+        getPage('http://bang.bigasterisk.com:9069/inputChange',
+                method="POST",
+                postdata=msg,
+                headers={'Content-Type' : 'application/json'}
+                ).addErrback(self.reportError, msg)
+
+    def reportError(self, msg, *args):
+        print "post error", msg, args
+
+if __name__ == '__main__':
+
+    config = { # to be read from a file
+        'arduinoPort': '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A4001lVK-if00-port0',
+        'servePort' : 9088,
+        'pollFrequency' : 6,
+        'boardName' : 'bedroom', # gets sent with updates
+        }
+
+    from twisted.python import log as twlog
+    #twlog.startLogging(sys.stdout)
+
+    log.setLevel(logging.DEBUG)
+
+    ard = ArduinoBedroom(port=config['arduinoPort'])
+
+    period = 1/config['pollFrequency']
+    p = Poller(config, ard, period)
+    task.LoopingCall(p.poll).start(period)
+
+    reactor.listenTCP(config['servePort'], cyclone.web.Application([
+        (r"/", Index),
+        (r"/graph", GraphPage),
+        (r'/speakerChoice', SpeakerChoice),
+        ], arduino=ard, poller=p))
+    reactor.run()