Mercurial > code > home > repos > homeauto
changeset 2:3a119010b960
moved from proj/room
Ignore-this: bb65f6f4b41c2687c00eef9cdb8ff730
author | drewp@bigasterisk.com |
---|---|
date | Sun, 07 Aug 2011 20:15:10 -0700 |
parents | 26a6cf58743d |
children | 6c2e83a0c85b |
files | service/bluetooth/bluetoothService.py |
diffstat | 1 files changed, 175 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/bluetooth/bluetoothService.py Sun Aug 07 20:15:10 2011 -0700 @@ -0,0 +1,175 @@ +#!/usr/bin/python + +""" +watch for bluetooth devices + +this discoverer finds me if my treo has its screen on only, so I +have to wake up my own treo for a few seconds. + +I can use 'hcitool cc <addr> && hcitool rssi <addr>' to wake it up and +get its signal strength, but that pattern crashes my treo easily. I +still don't have an access that wakes up the treo and then doesn't +crash it. Maybe I could pretend to be a headset or something. + +depends on ubuntu package: python-bluez + +""" +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 rdflib.Graph import Graph +from rdflib import Literal, Variable, Namespace +from pymongo import Connection +from dateutil import tz + +sys.path.append("/my/proj/homeauto/lib") +from cycloneerr import PrettyErrorHandler +from logsetup import log + +mongo = Connection('bang', 27017)['visitor']['visitor'] + +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 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) + + 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/" + ) + +def mongoInsert(msg): + try: + js = jsonlib.dumps(msg) + except UnicodeDecodeError: + pass + else: + if msg['name'] != 'THINKPAD_T43': + hub.post("visitorNet", payload=js) # sans datetime + msg['created'] = datetime.datetime.now(tz.gettz('UTC')) + mongo.insert(msg, safe=True) + +class Poller(object): + def __init__(self): + self.lastDevs = set() # addresses + self.lastNameForAddress = {} + self.currentGraph = Graph() + self.lastPollTime = 0 + + def poll(self): + log.debug("get devices") + devs = d.nearbyDevices() + + devs.addCallback(self.compare) + devs.addErrback(log.error) + return devs + + def compare(self, newDevs): + 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"}) + + 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) + +class Index(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + age = time.time() - self.settings.poller.lastPollTime + if age > 60 + 30: + raise ValueError("poll data is stale. age=%s" % age) + + self.write("bluetooth watcher. ") + +if __name__ == '__main__': + log.setLevel(logging.DEBUG) + poller = Poller() + reactor.listenTCP(9077, cyclone.web.Application([ + (r'/', Index), + ], poller=poller)) + task.LoopingCall(poller.poll).start(1) + reactor.run()