diff service/bluetooth/bluetoothService.py @ 4:be855a111619

move a bunch of services into this tree, give them all web status pages Ignore-this: a11e90f9d2cd9470565c743f54943c4b
author drewp@bigasterisk.com
date Mon, 08 Aug 2011 00:31:31 -0700
parents 3a119010b960
children f299b71f88f7
line wrap: on
line diff
--- a/service/bluetooth/bluetoothService.py	Sun Aug 07 21:50:21 2011 -0700
+++ b/service/bluetooth/bluetoothService.py	Mon Aug 08 00:31:31 2011 -0700
@@ -16,10 +16,11 @@
 """
 from __future__ import absolute_import
 import logging, time, datetime, restkit, jsonlib, cyclone.web, sys
-from bluetooth import DeviceDiscoverer
-from twisted.internet import reactor, defer, task
+from bluetooth import discover_devices, lookup_name
+from twisted.internet import reactor, task
+from twisted.internet.threads import deferToThread
 from rdflib.Graph import Graph
-from rdflib import Literal, Variable, Namespace
+from rdflib import Literal, Namespace, RDFS, URIRef
 from pymongo import Connection
 from dateutil import tz
 
@@ -31,61 +32,15 @@
 
 ROOM = Namespace("http://projects.bigasterisk.com/room/")
 
-class Disco(DeviceDiscoverer):
-    # it might be cool if this somehow returned
-    # _bt.EVT_INQUIRY_RESULT_WITH_RSSI: results. see
-    # /usr/share/pycentral/python-bluez/site-packages/bluetooth.py
-    def device_discovered(self, address, device_class, name):
-        log.debug("seeing: %s - %s (class 0x%X)" % (address, name, device_class))
-        self.nearby.append((address, name))
-
-    def inquiry_complete(self):
-        pass
-    
-    def process_inquiry(self):
-        # more async version of the normal method
-        """
-        Starts calling process_event, returning a deferred that fires
-        when we're done.
-        """
-        self.done_inquiry = defer.Deferred()
-        
-        if self.is_inquiring or len(self.names_to_find) > 0:
-            self.keep_processing()
-        else:
-            self.done_inquiry.callback(None)
-
-        return self.done_inquiry
+def getNearbyDevices():
+    addrs = discover_devices()
 
-    def keep_processing(self):
-        # this one still blocks "a little bit"
-        if self.is_inquiring or len(self.names_to_find) > 0:
-            reactor.callLater(0, self.keep_processing)
-            log.debug("process_event()")
-            self.process_event() # <-- blocks here
-        else:
-            self.done_inquiry.callback(None)
+    # this can be done during discover_devices, but my plan was to
+    # cache it more in here
+    names = dict((a, lookup_name(a)) for a in addrs)
+    log.debug("discover found %r %r", addrs, names)
+    return addrs, names
 
-    def nearbyDevices(self):
-        """deferred to list of (addr,name) pairs"""
-        self.nearby = []
-        self.find_devices()
-        d = self.process_inquiry()
-        d.addCallback(lambda result: self.nearby)
-        return d
-
-def devicesFromAddress(address):
-    for row in graph.query(
-        "SELECT ?dev { ?dev rm:bluetoothAddress ?addr }",
-        initNs=dict(rm=ROOM),
-        initBindings={Variable("?addr") : Literal(address)}):
-        (dev,) = row
-        yield dev
-                        
-graph = Graph()
-graph.parse("phones.n3", format="n3")
-
-d = Disco()
 hub = restkit.Resource(
     # PSHB not working yet; "http://bang:9030/"
     "http://slash:9049/"
@@ -102,74 +57,66 @@
     msg['created'] = datetime.datetime.now(tz.gettz('UTC'))
     mongo.insert(msg, safe=True)
 
+def deviceUri(addr):
+    return URIRef("http://bigasterisk.com/bluetooth/%s" % addr)
+
 class Poller(object):
     def __init__(self):
-        self.lastDevs = set() # addresses
-        self.lastNameForAddress = {}
+        self.lastAddrs = set() # addresses
         self.currentGraph = Graph()
         self.lastPollTime = 0
 
     def poll(self):
         log.debug("get devices")
-        devs = d.nearbyDevices()
+        devs = deferToThread(getNearbyDevices)
 
         devs.addCallback(self.compare)
         devs.addErrback(log.error)
         return devs
 
-    def compare(self, newDevs):
+    def compare(self, (addrs, names)):
         self.lastPollTime = time.time()
-        log.debug("got: %r", newDevs)
-        lostDevs = self.lastDevs.copy()
-        prevDevs = self.lastDevs.copy()
-        self.lastDevs.clear()
-        stmts = []
-
-        for address, name in newDevs:
-            stmts.append((ROOM['bluetooth'],
-                          ROOM['senses'],
-                          Literal(str(address))))
-            if address not in prevDevs:
-                matches = 0
-                for dev in devicesFromAddress(address):
-                    log.info("found %s" % dev)
-                    matches += 1
-                if not matches:
-                    log.info("no matches for %s (%s)" % (name, address))
 
-                    print "%s %s %s" % (time.time(), name, address)
-
-                self.lastNameForAddress[address] = name
-                print 'mongoInsert', ({"sensor" : "bluetooth",
-                             "address" : address,
-                             "name" : name,
-                             "action" : "arrive"})
+        newGraph = Graph()
+        addrs = set(addrs)
+        for addr in addrs.difference(self.lastAddrs):
+            self.recordAction('arrive', addr, names)
+        for addr in self.lastAddrs.difference(addrs):
+            self.recordAction('leave', addr, names)
+        for addr in addrs:
+            uri = deviceUri(addr)
+            newGraph.add((ROOM['bluetooth'], ROOM['senses'], uri))
+            if addr in names:
+                newGraph.add((uri, RDFS.label, Literal(names[addr])))
+        self.lastAddrs = addrs
+        self.currentGraph = newGraph
 
-            lostDevs.discard(address)
-            self.lastDevs.add(address)
-
-        for address in lostDevs:
-            print 'mongoInsert', ({"sensor" : "bluetooth",
-                         "address" : address,
-                         "name" : self.lastNameForAddress[address],
-                         "action" : "leave"})
-
-            for dev in devicesFromAddress(address):
-                log.info("lost %s" % dev)
+    def recordAction(self, action, addr, names):
+        doc = {"sensor" : "bluetooth",
+               "address" : addr,
+               "action" : action}
+        if addr in names:
+            doc["name"] = names[addr]
+        log.info("action: %s", doc)
+        mongoInsert(doc)
 
 class Index(PrettyErrorHandler, cyclone.web.RequestHandler):
     def get(self):
         age = time.time() - self.settings.poller.lastPollTime
-        if age > 60 + 30:
+        if age > self.settings.config['period'] + 30:
             raise ValueError("poll data is stale. age=%s" % age)
         
         self.write("bluetooth watcher. ")
 
 if __name__ == '__main__':
-    log.setLevel(logging.DEBUG)
+    config = {
+        "period" : 60,
+        }
+    log.setLevel(logging.INFO)
     poller = Poller()
     reactor.listenTCP(9077, cyclone.web.Application([
         (r'/', Index),
-        ], poller=poller))
-    task.LoopingCall(poller.poll).start(1)
+        # graph, json, table, ...
+        ], poller=poller, config=config))
+    task.LoopingCall(poller.poll).start(config['period'])
     reactor.run()