changeset 935:9bb3eac740f0

new shuttlepro web service with /graph Ignore-this: 1c8e71aad6ab24fa24532f4e5e01d2d9 darcs-hash:20131012053641-312f9-c29b698a215697c0dc4955bdaada3923ef8b6d28
author drewp <drewp@bigasterisk.com>
date Fri, 11 Oct 2013 22:36:41 -0700
parents 3bb18b7d21df
children b1eabeb7dc66
files service/shuttlepro/gui.js service/shuttlepro/index.html service/shuttlepro/shuttlepro.py service/shuttlepro/shuttleservice.py
diffstat 4 files changed, 146 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/shuttlepro/gui.js	Fri Oct 11 22:36:41 2013 -0700
@@ -0,0 +1,20 @@
+(function () {
+    function abbreviateTrig(trig) {
+        // prefixes, abbreviations, make everything into links, etc
+        return trig;
+    }
+    
+    var model = {
+        current: ko.observable("...")
+    };
+    model.refreshGraph = function() {
+        $.ajax({
+            url: "graph",
+            success: function(data) {
+                model.current(abbreviateTrig(data));
+            }
+        });
+    };
+    model.refreshGraph();
+    ko.applyBindings(model);   
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/shuttlepro/index.html	Fri Oct 11 22:36:41 2013 -0700
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf8">
+    <style>
+      pre {
+        font-family: sans-serif;
+        font-size: 75%;
+      }
+      </style>
+  </head>
+  <body>
+    <h1>shuttlepro</h1>
+
+    <div class="connectionDiagram"><a href="#">host slash</a> → <a href="#">shuttlepro in living room</a></div>
+
+    <p>pics</p>
+
+    <section>
+      <h2>Current state</h2>
+      <pre data-bind="text: current"></pre>
+      <button data-bind="click: refreshGraph">Refresh</button>
+    </section>
+    <script src="//bigasterisk.com/lib/jquery-2.0.3.min.js"></script>
+    <script src="//bigasterisk.com/lib/knockout-2.3.0.js"></script>
+    <script src="gui.js"></script>
+    
+  </body>
+</html>
+       
--- a/service/shuttlepro/shuttlepro.py	Tue Oct 08 21:42:24 2013 -0700
+++ b/service/shuttlepro/shuttlepro.py	Fri Oct 11 22:36:41 2013 -0700
@@ -25,7 +25,6 @@
 
 modified by drewp@bigasterisk.com
 """
-
 import os, time, logging
 import sys
 import struct
@@ -354,43 +353,10 @@
 
         return event
 
-import restkit
-reasoning = restkit.Resource("http://bang:9071/", timeout=1)
-
-from rdflib import Namespace, Graph, Literal
-SHUTTLEPRO = Namespace("http://bigasterisk.com/room/livingRoom/shuttlepro/")
-ROOM = Namespace("http://projects.bigasterisk.com/room/")
 
 if __name__ == '__main__':
-    import restkit
-    reasoning = restkit.Resource("http://bang:9071/", timeout=1)
-
-    # this should serve a graph of the current state as well, not just all oneshots
-    
     def ev(what):
         print 'ev', what
-        g = Graph()
-        if 'key' in what:
-            g.add((SHUTTLEPRO['button%s' % what['key']['button']],
-                   ROOM['state'],
-                   ROOM['press'] if what['key']['press'] else ROOM['release']))
-        elif 'shuttle' in what:
-            # this will send lots of repeats. It's really not a one-shot at all.
-            g.add((SHUTTLEPRO['shuttle'], ROOM['position'],
-                   Literal(what['shuttle'])))
-        elif 'dial' in what:
-            g.add((SHUTTLEPRO['dial'], ROOM['change'],
-                   ROOM['clockwise'] if what['dial'] == 1 else
-                   ROOM['counterclockwise']))
-        try:
-            nt = g.serialize(format='nt')
-            reasoning.post(
-              "oneShot",
-              payload=nt,
-              headers={'Content-Type': 'text/n3'},
-            ).body_string()
-        except restkit.errors.RequestTimeout, e:
-          log.error(e)
 
     p = powermate("/dev/input/by-id/usb-Contour_Design_ShuttlePRO-event-if00", ev)
     while True:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/shuttlepro/shuttleservice.py	Fri Oct 11 22:36:41 2013 -0700
@@ -0,0 +1,96 @@
+import sys, time
+import cyclone.web, cyclone.httpclient, cyclone.websocket
+sys.path.append("../../lib")
+from logsetup import log
+from cycloneerr import PrettyErrorHandler
+from twisted.internet import reactor, threads
+from shuttlepro import powermate
+sys.path.append("/my/site/magma")
+from stategraph import StateGraph
+
+from rdflib import Namespace, Graph, Literal
+SHUTTLEPRO = Namespace("http://bigasterisk.com/room/livingRoom/shuttlepro/")
+ROOM = Namespace("http://projects.bigasterisk.com/room/")
+
+def sendOneShotGraph(g):
+    if not g:
+        return
+
+    nt = g.serialize(format='nt')
+    cyclone.httpclient.fetch(
+        "http://bang:9071/oneShot",
+        method='POST',
+        postdata=nt,
+        timeout=1,
+        headers={'Content-Type': ['text/n3']},
+    ).addErrback(log.error)
+
+class Index(PrettyErrorHandler, cyclone.web.StaticFileHandler):
+    def get(self, *args, **kw):
+        self.settings.poller.assertCurrent()
+        return cyclone.web.StaticFileHandler.get(self, *args, **kw)
+
+class GraphResource(PrettyErrorHandler, cyclone.web.RequestHandler):
+    def get(self):
+        p = self.settings.poller
+        g = StateGraph(ROOM.environment)
+        g.add((SHUTTLEPRO['shuttle'], ROOM['angle'],
+               Literal(p.currentShuttleAngle)))
+        g.add((SHUTTLEPRO['dial'], ROOM['totalDialMovement'],
+               Literal(p.totalDialMovement)))
+        self.set_header('Content-type', 'application/x-trig')
+        self.write(g.asTrig())
+
+class Poller(object):
+    def __init__(self, dev="/dev/input/by-id/usb-Contour_Design_ShuttlePRO-event-if00"):
+        self.lastUpdateTime = 0
+        self.p = powermate(dev, self.onEvent)
+        self.updateLoop()
+        self.currentShuttleAngle = 0
+        self.totalDialMovement = 0
+
+    def assertCurrent(self):
+        ago = time.time() - self.lastUpdateTime
+        if ago > 60: # this may have to go up depending on how read_next times out
+            raise ValueError("last usb update was %s sec ago" % ago)
+        
+    def onEvent(self, what):
+        print 'onEvent', what
+        g = Graph()
+        if 'key' in what:
+            g.add((SHUTTLEPRO['button%s' % what['key']['button']],
+                   ROOM['state'],
+                   ROOM['press'] if what['key']['press'] else ROOM['release']))
+        elif 'shuttle' in what:
+            # this will send lots of repeats. It's really not a one-shot at all.
+            g.add((SHUTTLEPRO['shuttle'], ROOM['position'],
+                   Literal(what['shuttle'])))
+            self.currentShuttleAngle = what['shuttle']
+        elif 'dial' in what:
+            g.add((SHUTTLEPRO['dial'], ROOM['change'],
+                   ROOM['clockwise'] if what['dial'] == 1 else
+                   ROOM['counterclockwise']))
+            self.totalDialMovement += what['dial']
+        sendOneShotGraph(g)
+
+    def updateLoop(self, *prevResults):
+        self.lastUpdateTime = time.time()
+        threads.deferToThread(
+            self.p.read_next
+        ).addCallback(self.updateLoop)
+        
+if __name__ == '__main__':
+    from twisted.python import log as twlog
+    twlog.startLogging(sys.stdout)
+    port = 9103
+    poller = Poller()
+    reactor.listenTCP(port, cyclone.web.Application(handlers=[
+        (r'/()', Index, {"path" : ".", # security hole- serves this dir too
+          "default_filename" : "index.html"}),
+        (r'/graph', GraphResource),
+        (r'/(.*)', cyclone.web.StaticFileHandler,
+         {"path" : ".", # security hole- serves this dir too
+          "default_filename" : "index.html"}),
+        ], poller=poller))
+    log.info("serving on %s" % port)
+    reactor.run()