Mercurial > code > home > repos > homeauto
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()