changeset 129:745eff67ad40

reasoning actions: generalize them a bit but then add a bunch of special cases for mpd for now Ignore-this: 3ab80fee5836817dcd54ce6678a6089d
author drewp@bigasterisk.com
date Tue, 08 Oct 2013 21:42:24 -0700
parents 06ad4a0f42c0
children 57ae7dc0f417
files service/reasoning/input/startup.n3 service/reasoning/oneShot service/reasoning/rdflibtrig.py service/reasoning/reasoning.py service/reasoning/rules.n3
diffstat 5 files changed, 90 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/service/reasoning/input/startup.n3	Tue Oct 08 21:39:06 2013 -0700
+++ b/service/reasoning/input/startup.n3	Tue Oct 08 21:42:24 2013 -0700
@@ -18,3 +18,9 @@
 
 # also, http://bang:9072/bang/processStatus
 
+
+[ a :OneShotPost; :subject <http://bigasterisk.com/host/star/slideshow>; :predicate :postAction ] .
+[ a :OneShotPost; :subject <http://bigasterisk.com/host/star/sound>; :predicate :postAction ] .
+[ a :OneShotPost; :subject <http://bigasterisk.com/host/slash/sound>; :predicate :postAction ] .
+
+[ a :OneShotPost; :subject <http://bigasterisk.com/host/slash/mpd>; :predicate :postAction ] .
--- a/service/reasoning/oneShot	Tue Oct 08 21:39:06 2013 -0700
+++ b/service/reasoning/oneShot	Tue Oct 08 21:42:24 2013 -0700
@@ -8,10 +8,11 @@
 
 prefixes = {
 'room' : 'http://projects.bigasterisk.com/room/',
+'shuttle': 'http://bigasterisk.com/room/livingRoom/shuttlepro/',
     }
 
 def expand(term):
-    if ':' not in term:
+    if ':' not in term or term.startswith(('<', '"', "'")):
         return term
     left, right = term.split(':', 1)
     if left in prefixes:
--- a/service/reasoning/rdflibtrig.py	Tue Oct 08 21:39:06 2013 -0700
+++ b/service/reasoning/rdflibtrig.py	Tue Oct 08 21:42:24 2013 -0700
@@ -24,9 +24,9 @@
         yield stmt + (ctx,)
 
         
-def addTrig(graph, url):
+def addTrig(graph, url, timeout=2):
     t1 = time.time()
-    response = restkit.request(url)
+    response = restkit.request(url, timeout=timeout)
     if response.status_int != 200:
         raise ValueError("status %s from %s" % (response.status, url))
     trig = response.body_string()
--- a/service/reasoning/reasoning.py	Tue Oct 08 21:39:06 2013 -0700
+++ b/service/reasoning/reasoning.py	Tue Oct 08 21:42:24 2013 -0700
@@ -19,7 +19,7 @@
 from twisted.internet import reactor, task
 from twisted.web.client import getPage
 from twisted.python.filepath import FilePath
-import time, traceback, sys, json, logging
+import time, traceback, sys, json, logging, urllib
 from rdflib.Graph import Graph, ConjunctiveGraph
 from rdflib import Namespace, URIRef, Literal, RDF, StringInputSource
 from FuXi.Rete.RuleStore import N3RuleStore
@@ -244,8 +244,8 @@
 
     def _put(self, url, payload):
         def err(e):
-            outlog.warn("put %s failed", url)
-        outlog.info("PUT %s payload=%r", url, payload)
+            outlog.warn("    put %s failed", url)
+        outlog.info("    PUT %s payload=%r", url, payload)
         fetch(url, method="PUT", postdata=payload, timeout=2).addErrback(err)
 
     def putResults(self, inferred):
@@ -290,29 +290,66 @@
 
 
     def oneShotPostActions(self, deviceGraph, inferred):
+        """
+        Inferred graph may contain some one-shot statements. We'll send
+        statement objects to anyone on web sockets, and also generate
+        POST requests as described in the graph.
+
+        one-shot statement ?s ?p ?o
+        with this in the graph:
+          ?osp a :OneShotPost
+          ?osp :subject ?s
+          ?osp :predicate ?p
+        this will cause a post to ?o 
+        """
         # nothing in this actually makes them one-shot yet. they'll
         # just fire as often as we get in here, which is not desirable
-        for s, p in [
-                (URIRef('http://bigasterisk.com/host/star/slideshow'), ROOM.postAction),
-                (URIRef('http://bigasterisk.com/host/star/sound'), ROOM.postAction),
-                (URIRef('http://bigasterisk.com/host/slash/sound'), ROOM.postAction),
-            ]:
-            log.info("find inferred objs %r %r" % (s, p))
+        log.info("oneShotPostActions")
+        def err(e):
+            outlog.warn("post %s failed", postTarget)
+        for osp in deviceGraph.subjects(RDF.type, ROOM['OneShotPost']):
+            s = deviceGraph.value(osp, ROOM['subject'])
+            p = deviceGraph.value(osp, ROOM['predicate'])
+            if s is None or p is None:
+                continue
             for postTarget in inferred.objects(s, p):
                 log.info("post target %r", postTarget)
+                # this packet ought to have 'oneShot' in it somewhere
                 sendToLiveClients({"s":s, "p":p, "o":postTarget})
-                if s in [URIRef('http://bigasterisk.com/host/star/sound'),
-                         URIRef('http://bigasterisk.com/host/slash/sound'),
-                         URIRef('http://bigasterisk.com/host/star/slideshow'),
-                        ]:
-                    try:
-                        response = restkit.request(url=postTarget, method="POST", body="")
-                    except Exception, e:
-                        log.warn("post to %s failed: %s" % (postTarget, e))
-                    else:
-                        log.info("post to %s got status %s" %
-                                 (postTarget, response.status))
 
+                outlog.info("    POST %s", postTarget)
+                fetch(postTarget, method="POST", timeout=2).addErrback(err)
+        self.postMpdCommands(inferred)
+        
+    def postMpdCommands(self, inferred):
+        """special case to be eliminated. mpd play urls are made of an
+        mpd service and a song/album/playlist uri to be played.
+        Ideally the graph rules would assemble these like
+        http://{mpd}/addAndPlay?uri={toPlay} or maybe toPlay as the payload
+        which would be fairly general but still allow toPlay uris to
+        be matched with any player."""
+        def post(postTarget):
+            outlog.info("special mpd POST %s", postTarget)
+            def err(e):
+                outlog.warn("post %s failed", postTarget)
+            fetch(postTarget, method="POST", timeout=2).addErrback(err)
+        root = "http://bigasterisk.com/music/slash/mpd/"
+        rootSkippingAuth = "http://slash:9009/"
+        slashMpd = URIRef("http://bigasterisk.com/host/slash/mpd")
+        for song in inferred.objects(slashMpd, ROOM['startMusic']):
+            outlog.info("mpd statement: %r" % song)
+            assert song.startswith('http://bigasterisk.com/music/')
+            post(rootSkippingAuth + "addAndPlay" + urllib.quote(song[len("http://bigasterisk.com/music"):]))
+            
+        for state in inferred.objects(slashMpd, ROOM['playState']):
+            if state == ROOM['pause']:
+                post(rootSkippingAuth + "mpd/pause")
+        for vol in inferred.objects(slashMpd, ROOM['audioState']):
+            if vol == ROOM['volumeStepUp']:
+                post(rootSkippingAuth + "volumeAdjust?amount=6&max=70")
+            if vol == ROOM['volumeStepDown']:
+                post(rootSkippingAuth + "volumeAdjust?amount=-6&min=10")
+            
     def putZero(self, deviceGraph, dev, pred, putUrl):
         # zerovalue should be a function of pred as well.
         value = deviceGraph.value(dev, ROOM.zeroValue)
@@ -399,6 +436,9 @@
         everything appears to be a 'change'.
         """
         g = parseRdf(self.request.body, self.request.headers['content-type'])
+        if not len(g):
+            log.warn("incoming oneshot graph had no statements: %r", self.request.body)
+            return
         self.settings.reasoning.inputGraph.addOneShot(g)
 
 # for reuse
--- a/service/reasoning/rules.n3	Tue Oct 08 21:39:06 2013 -0700
+++ b/service/reasoning/rules.n3	Tue Oct 08 21:42:24 2013 -0700
@@ -1,3 +1,5 @@
+# rules only! statements in this file will not be considered in the graph
+
 @prefix : <http://projects.bigasterisk.com/room/>.
 @prefix bigast: <http://bigasterisk.com/>.
 @prefix dev: <http://projects.bigasterisk.com/device/>.
@@ -58,9 +60,17 @@
 
 { <http://projects.bigasterisk.com/room/star/button/yel> :change :down . } =>
 {
-  <http://bigasterisk.com/host/star/sound> :postAction <http://star:9049/sound?filename=/my/music/ubuntuone/Daft+Punk/Discovery/Harder+Better+Faster+Stronger.mp3> .
+  <http://bigasterisk.com/host/star/slideshow> :postAction <http://bigasterisk.com/host/star/slideshow/toggleFeeder> .
+  <http://bigasterisk.com/host/star/slideshow> :postAction <http://star:9049/effects/beep1> .
 } .
 
+
+# sound wasn't responding; waiting for other button fixes
+# { <http://projects.bigasterisk.com/room/star/button/yel> :change :down . } =>
+# {
+#   <http://bigasterisk.com/host/star/sound> :postAction <http://star:9049/sound?filename=/my/music/ubuntuone/Daft+Punk/Discovery/Harder+Better+Faster+Stronger.mp3> .
+# } .
+
 { <http://projects.bigasterisk.com/room/livingRoom/shuttlepro/dial> :change :up . } => {
   <http://bigasterisk.com/host/slash/sound> :postAction <http://slash:9049/volume?offset=.05&max=.6> .
 } .
@@ -83,3 +93,12 @@
   # the plan here is that as soon as we can show that I'm not at my
   # desk (cell phone wifi, asleep, etc), power the screen off
 } .
+
+@prefix shuttle: <http://bigasterisk.com/room/livingRoom/shuttlepro/> .
+@prefix mpd: <http://bigasterisk.com/host/slash/mpd> .
+
+{ ?button :state :press . ?button :playsMusic ?song } => { mpd: :startMusic ?song } .
+# the rest of this modeling is still a mess. Handled completely by special case in reasoning.py
+{ shuttle:button12 :state :press         } => { mpd: :playState :pause } .
+{ shuttle:dial :change :clockwise        } => { mpd: :audioState :volumeStepUp } .
+{ shuttle:dial :change :counterclockwise } => { mpd: :audioState :volumeStepDown } .