comparison service/bluetooth/bluetoothService.py @ 807:4713bb87e34e

moved from proj/room Ignore-this: bb65f6f4b41c2687c00eef9cdb8ff730 darcs-hash:20110808031510-312f9-a009d540c5a76ce231387581cb9e2d43efe4b3ae.gz
author drewp <drewp@bigasterisk.com>
date Sun, 07 Aug 2011 20:15:10 -0700
parents
children be855a111619
comparison
equal deleted inserted replaced
806:85e50a597244 807:4713bb87e34e
1 #!/usr/bin/python
2
3 """
4 watch for bluetooth devices
5
6 this discoverer finds me if my treo has its screen on only, so I
7 have to wake up my own treo for a few seconds.
8
9 I can use 'hcitool cc <addr> && hcitool rssi <addr>' to wake it up and
10 get its signal strength, but that pattern crashes my treo easily. I
11 still don't have an access that wakes up the treo and then doesn't
12 crash it. Maybe I could pretend to be a headset or something.
13
14 depends on ubuntu package: python-bluez
15
16 """
17 from __future__ import absolute_import
18 import logging, time, datetime, restkit, jsonlib, cyclone.web, sys
19 from bluetooth import DeviceDiscoverer
20 from twisted.internet import reactor, defer, task
21 from rdflib.Graph import Graph
22 from rdflib import Literal, Variable, Namespace
23 from pymongo import Connection
24 from dateutil import tz
25
26 sys.path.append("/my/proj/homeauto/lib")
27 from cycloneerr import PrettyErrorHandler
28 from logsetup import log
29
30 mongo = Connection('bang', 27017)['visitor']['visitor']
31
32 ROOM = Namespace("http://projects.bigasterisk.com/room/")
33
34 class Disco(DeviceDiscoverer):
35 # it might be cool if this somehow returned
36 # _bt.EVT_INQUIRY_RESULT_WITH_RSSI: results. see
37 # /usr/share/pycentral/python-bluez/site-packages/bluetooth.py
38 def device_discovered(self, address, device_class, name):
39 log.debug("seeing: %s - %s (class 0x%X)" % (address, name, device_class))
40 self.nearby.append((address, name))
41
42 def inquiry_complete(self):
43 pass
44
45 def process_inquiry(self):
46 # more async version of the normal method
47 """
48 Starts calling process_event, returning a deferred that fires
49 when we're done.
50 """
51 self.done_inquiry = defer.Deferred()
52
53 if self.is_inquiring or len(self.names_to_find) > 0:
54 self.keep_processing()
55 else:
56 self.done_inquiry.callback(None)
57
58 return self.done_inquiry
59
60 def keep_processing(self):
61 # this one still blocks "a little bit"
62 if self.is_inquiring or len(self.names_to_find) > 0:
63 reactor.callLater(0, self.keep_processing)
64 log.debug("process_event()")
65 self.process_event() # <-- blocks here
66 else:
67 self.done_inquiry.callback(None)
68
69 def nearbyDevices(self):
70 """deferred to list of (addr,name) pairs"""
71 self.nearby = []
72 self.find_devices()
73 d = self.process_inquiry()
74 d.addCallback(lambda result: self.nearby)
75 return d
76
77 def devicesFromAddress(address):
78 for row in graph.query(
79 "SELECT ?dev { ?dev rm:bluetoothAddress ?addr }",
80 initNs=dict(rm=ROOM),
81 initBindings={Variable("?addr") : Literal(address)}):
82 (dev,) = row
83 yield dev
84
85 graph = Graph()
86 graph.parse("phones.n3", format="n3")
87
88 d = Disco()
89 hub = restkit.Resource(
90 # PSHB not working yet; "http://bang:9030/"
91 "http://slash:9049/"
92 )
93
94 def mongoInsert(msg):
95 try:
96 js = jsonlib.dumps(msg)
97 except UnicodeDecodeError:
98 pass
99 else:
100 if msg['name'] != 'THINKPAD_T43':
101 hub.post("visitorNet", payload=js) # sans datetime
102 msg['created'] = datetime.datetime.now(tz.gettz('UTC'))
103 mongo.insert(msg, safe=True)
104
105 class Poller(object):
106 def __init__(self):
107 self.lastDevs = set() # addresses
108 self.lastNameForAddress = {}
109 self.currentGraph = Graph()
110 self.lastPollTime = 0
111
112 def poll(self):
113 log.debug("get devices")
114 devs = d.nearbyDevices()
115
116 devs.addCallback(self.compare)
117 devs.addErrback(log.error)
118 return devs
119
120 def compare(self, newDevs):
121 self.lastPollTime = time.time()
122 log.debug("got: %r", newDevs)
123 lostDevs = self.lastDevs.copy()
124 prevDevs = self.lastDevs.copy()
125 self.lastDevs.clear()
126 stmts = []
127
128 for address, name in newDevs:
129 stmts.append((ROOM['bluetooth'],
130 ROOM['senses'],
131 Literal(str(address))))
132 if address not in prevDevs:
133 matches = 0
134 for dev in devicesFromAddress(address):
135 log.info("found %s" % dev)
136 matches += 1
137 if not matches:
138 log.info("no matches for %s (%s)" % (name, address))
139
140 print "%s %s %s" % (time.time(), name, address)
141
142 self.lastNameForAddress[address] = name
143 print 'mongoInsert', ({"sensor" : "bluetooth",
144 "address" : address,
145 "name" : name,
146 "action" : "arrive"})
147
148 lostDevs.discard(address)
149 self.lastDevs.add(address)
150
151 for address in lostDevs:
152 print 'mongoInsert', ({"sensor" : "bluetooth",
153 "address" : address,
154 "name" : self.lastNameForAddress[address],
155 "action" : "leave"})
156
157 for dev in devicesFromAddress(address):
158 log.info("lost %s" % dev)
159
160 class Index(PrettyErrorHandler, cyclone.web.RequestHandler):
161 def get(self):
162 age = time.time() - self.settings.poller.lastPollTime
163 if age > 60 + 30:
164 raise ValueError("poll data is stale. age=%s" % age)
165
166 self.write("bluetooth watcher. ")
167
168 if __name__ == '__main__':
169 log.setLevel(logging.DEBUG)
170 poller = Poller()
171 reactor.listenTCP(9077, cyclone.web.Application([
172 (r'/', Index),
173 ], poller=poller))
174 task.LoopingCall(poller.poll).start(1)
175 reactor.run()