changeset 426:bfe555dd0c91

talk to store graph, second button for holding unlocked, etc Ignore-this: c2ae7d756e743c26e5e01d99772899bd
author drewp@bigasterisk.com
date Thu, 04 Apr 2019 02:16:22 -0700
parents d495d4382a07
children db031d9ec28e
files service/frontDoorLock/front_door_lock.py service/frontDoorLock/index.html service/frontDoorLock/makefile service/frontDoorLock/requirements.txt
diffstat 4 files changed, 126 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/service/frontDoorLock/front_door_lock.py	Thu Apr 04 02:14:48 2019 -0700
+++ b/service/frontDoorLock/front_door_lock.py	Thu Apr 04 02:16:22 2019 -0700
@@ -12,7 +12,7 @@
 from rdflib.parser import StringInputSource
 from twisted.internet import reactor, task
 import cyclone.web
-import sys, logging, time
+import logging, time, json
 from mqtt_client import MqttClient
 from logsetup import log, enableTwistedLog
 
@@ -39,6 +39,12 @@
     
 class OutputPage(cyclone.web.RequestHandler):
     def put(self):
+        try:
+            user = URIRef(self.request.headers['x-foaf-agent'])
+        except KeyError:
+            log.warn('request without x-foaf-agent: %s', self.request.headers)
+            self.set_status(403, 'need x-foaf-agent')
+            return
         arg = self.request.arguments
         if arg.get('s') and arg.get('p'):
             subj = URIRef(arg['s'][-1])
@@ -49,11 +55,17 @@
             g = rdfGraphBody(self.request.body, self.request.headers)
             assert len(g) == 1, len(g)
             stmt = g.triples((None, None, None)).next()
-        self._onStatement(stmt)
+        self._onStatement(user, stmt)
     post = put
     
-    def _onStatement(self, stmt):
+    def _onStatement(self, user, stmt):
+        log.info('put statement %r', stmt)
         if stmt[0:2] == (ROOM['frontDoorLock'], ROOM['state']):
+            if stmt[2] == ROOM['unlocked']:
+                log.info('unlock for %r', user)
+                self.settings.autoLock.onUnlockedStmt()
+            if stmt[2] == ROOM['locked']:
+                self.settings.autoLock.onLockedStmt()
             self.settings.mqtt.publish("frontdoor/switch/strike/command",
                                        mqttMessageFromState(stmt[2]))
             return
@@ -65,24 +77,44 @@
         self.masterGraph = masterGraph
         self.mqtt = mqtt
         self.timeUnlocked = None
-        self.autoLockSec = 5
+        self.autoLockSec = 6 
         self.subj = ROOM['frontDoorLock']
         task.LoopingCall(self.check).start(1)
 
+    def relock(self):
+        log.info('autolock is up: requesting lock')
+        self.mqtt.publish("frontdoor/switch/strike/command",
+                          mqttMessageFromState(ROOM['locked']))
+
+    def reportTimes(self, unlockedFor):
+        g = self.masterGraph
+        lockIn = self.autoLockSec - int(unlockedFor)
+        if lockIn < 0:
+            tu = self.timeUnlocked
+            log.warn("timeUnlocked %(tu)r, state %(state)s, "
+                     "unlockedFor %(unlockedFor)r, lockIn %(lockIn)r", vars())
+            lockIn = 0
+        g.patchObject(ctx, self.subj, ROOM['unlockedForSec'],
+                      Literal(int(unlockedFor)))
+        g.patchObject(ctx, self.subj, ROOM['autoLockInSec'],
+                      Literal(lockIn))
+
+    def clearReport(self):
+        g = self.masterGraph
+        g.patchObject(ctx, self.subj, ROOM['unlockedForSec'], None)
+        g.patchObject(ctx, self.subj, ROOM['autoLockInSec'], None)
+        
     def check(self):
+        g = self.masterGraph
         now = time.time()
-        state = self.masterGraph._graph.value(self.subj, ROOM['state'])
+        state = g._graph.value(self.subj, ROOM['state'])
         if state == ROOM['unlocked']:
             if self.timeUnlocked is None:
                 self.timeUnlocked = now
+            # *newly* unlocked- this resets on every input stmt
             unlockedFor = now - self.timeUnlocked
-            self.masterGraph.patchObject(ctx, self.subj, ROOM['unlockedForSec'],
-                                         Literal(int(unlockedFor)))
-            self.masterGraph.patchObject(ctx, self.subj, ROOM['autoLockInSec'],
-                                         Literal(self.autoLockSec - int(unlockedFor)))
             if unlockedFor > self.autoLockSec:
-                self.mqtt.publish("frontdoor/switch/strike/command",
-                                  mqttMessageFromState(ROOM['locked']))
+                self.relock()
         else:
             self.timeUnlocked = None
             self.masterGraph.patchObject(ctx, self.subj, ROOM['unlockedForSec'], None)
@@ -111,14 +143,21 @@
 
     mqtt.subscribe("frontdoor/switch/strike/state").subscribe(on_next=toGraph)
     port = 10011
-    reactor.listenTCP(port, cyclone.web.Application([
-        (r"/()", cyclone.web.StaticFileHandler,
-         {"path": ".", "default_filename": "index.html"}),
-        (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}),
-        (r"/graph/events", CycloneGraphEventsHandler,
-         {'masterGraph': masterGraph}),
-        (r'/output', OutputPage),
-        ], mqtt=mqtt, masterGraph=masterGraph, debug=arg['-v']), interface='::')
+    reactor.listenTCP(port, cyclone.web.Application(
+        [
+            (r"/()", cyclone.web.StaticFileHandler,
+             {"path": ".", "default_filename": "index.html"}),
+            (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}),
+            (r"/graph/events", CycloneGraphEventsHandler,
+             {'masterGraph': masterGraph}),
+            (r'/output', OutputPage),
+            (r'/bluetoothButton', BluetoothButton),
+        ],
+        mqtt=mqtt,
+        masterGraph=masterGraph,
+        autoLock=autoclose,
+        debug=arg['-v']),
+                      interface='::')
     log.warn('serving on %s', port)
 
     reactor.run()
--- a/service/frontDoorLock/index.html	Thu Apr 04 02:14:48 2019 -0700
+++ b/service/frontDoorLock/index.html	Thu Apr 04 02:16:22 2019 -0700
@@ -58,6 +58,7 @@
       <template>
         <div>
           <streamed-graph url="graph/events" graph="{{graph}}"></streamed-graph>
+          <streamed-graph url="/store/graph/events" graph="{{storeGraph}}"></streamed-graph>
         </div>
 
         <div id="form">
@@ -73,13 +74,42 @@
             predicate="room:state"
             object="room:unlocked"
           ></rdf-oneshot>
-          <button on-click="unlock">Unlock</button>
+          <button on-click="unlock"
+                  disabled$="[[!isLocked]]">Unlock 10 seconds</button>
           
-          <template is="dom-if" if="{{autoLockIsComing}}">
-            <div>
+            <div class$="invis-[[!autoLockIsComing]]">
               Locking in {{autoLockInSec}}
             </div>
-          </template>
+
+          <div>
+            <rdf-oneshot
+              id="hold"
+              post="/store/values"
+              subject="room:frontDoorLockRequest"
+              predicate="room:state"
+              object="room:unlocked"
+            ></rdf-oneshot>
+            <template is="dom-if" if="{{!isHeld}}">
+              <button on-click="hold">Hold unlocked</button>
+            </template>
+            <rdf-oneshot
+              id="releaseHold"
+              post="/store/values"
+              subject="room:frontDoorLockRequest"
+              predicate="room:state"
+              object="room:unset"
+            ></rdf-oneshot>
+            <rdf-oneshot
+              id="lockNow"
+              post="output"
+              subject="room:frontDoorLock"
+              predicate="room:state"
+              object="room:locked"
+            ></rdf-oneshot>
+            <template is="dom-if" if="{{isHeld}}">
+              <button on-click="releaseHold">Release hold; lock door</button>
+            </template>
+          </div>
         </div>
       </template>
       <script>
@@ -88,20 +118,26 @@
            is: 'door-control',
            properties: {
              graph: { type: Object, notify: true, observer: "_onGraph" },
+             storeGraph: { type: Object, notify: true, observer: "_onStoreGraph" },
              lockState: { type: String },
              autoLockIsComing: { type: Boolean },
              autoLockInSec: { type: String},
+             isHeld: { type: Boolean },
            },
            behaviors: [BigastUri],
            _onGraph: function(graph) {
              if (!graph.graph) return;
              const env = graph.graph.store.rdf;
+             const unlocked = env.createNamedNode('room:unlocked');
+             const locked = env.createNamedNode('room:locked');
+             this.isLocked = null;
              graph.graph.quadStore.quads(
                {subject: env.createNamedNode('room:frontDoorLock'),
                 predicate: env.createNamedNode('room:state'),
                },
                (q) => {
                  this.lockState = q.object.toString().replace(/.*\//, '');
+                 this.isLocked = q.object.equals(locked) ? true : (q.object.equals(unlocked) ? false : null);
                });
              
              this.autoLockIsComing = false;
@@ -112,11 +148,32 @@
                (q) => {
                  this.autoLockIsComing = true;
                  this.autoLockInSec = parseFloat(q.object.valueOf());
+               });          
+           },
+           _onStoreGraph: function(graph) {
+             if (!graph.graph) return;
+             const env = graph.graph.store.rdf;
+             const unlocked = env.createNamedNode('room:unlocked');
+             const locked = env.createNamedNode('room:locked');
+             this.isHeld = false;
+             graph.graph.quadStore.quads(
+               {subject: env.createNamedNode('room:frontDoorLockRequest'),
+                predicate: env.createNamedNode('room:state'),
+               },
+               (q) => {
+                 this.isHeld = q.object.equals(unlocked);
                });
            },
            unlock: function() {
              this.$.unlockOneshot.go();
-           }
+           },
+           hold: function () {
+             this.$.hold.go(); 
+           },
+           releaseHold: function() {
+             this.$.releaseHold.go();
+             this.$.lockNow.go(); // may race with releaseHold?
+           },
          });
        });
       </script>
--- a/service/frontDoorLock/makefile	Thu Apr 04 02:14:48 2019 -0700
+++ b/service/frontDoorLock/makefile	Thu Apr 04 02:16:22 2019 -0700
@@ -15,5 +15,8 @@
 shell:
 	docker run --rm -it --cap-add SYS_PTRACE --net=host bang6:5000/$(JOB)_x86:latest  /bin/sh
 
-local_run:
-	docker run --rm -it --net=host -v `pwd`/index.html:/opt/index.html bang6:5000/$(JOB)_x86:latest python ./front_door_lock.py 
+local_run: build_image
+	docker run --rm -it --net=host -v `pwd`/index.html:/opt/index.html bang6:5000/$(JOB)_x86:latest python ./front_door_lock.py -v
+
+redeploy: build_image
+	supervisorctl restart $(JOB)_$(PORT)
--- a/service/frontDoorLock/requirements.txt	Thu Apr 04 02:14:48 2019 -0700
+++ b/service/frontDoorLock/requirements.txt	Thu Apr 04 02:16:22 2019 -0700
@@ -2,5 +2,5 @@
 rdflib-jsonld==0.4.0
 rdflib==4.2.2
 twisted-mqtt==0.3.6
-https://projects.bigasterisk.com/rdfdb/rdfdb-0.6.0.tar.gz
+https://projects.bigasterisk.com/rdfdb/rdfdb-0.7.0.tar.gz
 rx==1.6.1