Mercurial > code > home > repos > homeauto
annotate service/tomatoWifi/tomatoWifi.py @ 857:1168b0cd90af
fix fetch calls in tomatowifi
Ignore-this: 7b9a676be589b2400a18a16b1d194343
darcs-hash:20130101015342-312f9-aee1c4367ee99002e4603344eb7eb3249dbdded2
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Mon, 31 Dec 2012 17:53:42 -0800 |
parents | 4b386fc51325 |
children | f8cc3d1baa85 |
rev | line source |
---|---|
805 | 1 #!/usr/bin/python |
2 """ | |
3 scrape the tomato router status pages to see who's connected to the | |
4 wifi access points. Includes leases that aren't currently connected. | |
5 | |
6 Returns: | |
7 json listing (for magma page) | |
8 rdf graph (for reasoning) | |
9 activity stream, when we start saving history | |
10 | |
11 Todo: this should be the one polling and writing to mongo, not entrancemusic | |
12 """ | |
13 from __future__ import division | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
14 import sys, cyclone.web, json, traceback, time, pystache, datetime, logging |
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
15 from cyclone.httpclient import fetch |
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
16 sys.path.append("/home/drewp/projects/photo/lib/python2.7/site-packages") |
805 | 17 from dateutil import tz |
18 from twisted.internet import reactor, task | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
19 from twisted.internet.defer import inlineCallbacks, returnValue |
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
20 |
805 | 21 |
22 from pymongo import Connection, DESCENDING | |
23 from rdflib import Namespace, Literal, URIRef | |
24 sys.path.append("/my/site/magma") | |
25 from stategraph import StateGraph | |
26 from wifi import Wifi | |
27 | |
28 sys.path.append("/my/proj/homeauto/lib") | |
29 from cycloneerr import PrettyErrorHandler | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
30 from logsetup import log |
805 | 31 |
841
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
32 import rdflib |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
33 from rdflib import plugin |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
34 plugin.register( |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
35 "sparql", rdflib.query.Processor, |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
36 "rdfextras.sparql.processor", "Processor") |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
37 plugin.register( |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
38 "sparql", rdflib.query.Result, |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
39 "rdfextras.sparql.query", "SPARQLQueryResult") |
ddc2cdbfde50
rdflib and jsonlib api updates
drewp <drewp@bigasterisk.com>
parents:
806
diff
changeset
|
40 |
805 | 41 DEV = Namespace("http://projects.bigasterisk.com/device/") |
42 ROOM = Namespace("http://projects.bigasterisk.com/room/") | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
43 reasoning = "http://bang:9071/" |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
44 |
805 | 45 class Index(PrettyErrorHandler, cyclone.web.RequestHandler): |
46 def get(self): | |
47 | |
48 age = time.time() - self.settings.poller.lastPollTime | |
49 if age > 10: | |
50 raise ValueError("poll data is stale. age=%s" % age) | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
51 |
805 | 52 self.write("this is wifiusage. needs index page that embeds the table") |
53 | |
54 class Table(PrettyErrorHandler, cyclone.web.RequestHandler): | |
55 def get(self): | |
56 def rowDict(addr): | |
57 addr['cls'] = "signal" if addr.get('signal') else "nosignal" | |
58 if 'lease' in addr: | |
59 addr['lease'] = addr['lease'].replace("0 days, ", "") | |
60 return addr | |
61 | |
62 self.set_header("Content-Type", "application/xhtml+xml") | |
63 self.write(pystache.render( | |
64 open("table.mustache").read(), | |
65 dict( | |
66 rows=sorted(map(rowDict, self.settings.poller.lastAddrs), | |
67 key=lambda a: (a.get('router'), | |
68 a.get('name'), | |
69 a.get('mac')))))) | |
70 | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
71 |
805 | 72 class Json(PrettyErrorHandler, cyclone.web.RequestHandler): |
73 def get(self): | |
74 self.set_header("Content-Type", "application/json") | |
75 age = time.time() - self.settings.poller.lastPollTime | |
76 if age > 10: | |
77 raise ValueError("poll data is stale. age=%s" % age) | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
78 self.write(json.dumps({"wifi" : self.settings.poller.lastAddrs, |
805 | 79 "dataAge" : age})) |
80 | |
81 class GraphHandler(PrettyErrorHandler, cyclone.web.RequestHandler): | |
82 def get(self): | |
83 g = StateGraph(ctx=DEV['wifi']) | |
84 | |
85 # someday i may also record specific AP and their strength, | |
86 # for positioning. But many users just want to know that the | |
87 # device is connected to some bigasterisk AP. | |
88 aps = URIRef("http://bigasterisk.com/wifiAccessPoints") | |
89 age = time.time() - self.settings.poller.lastPollTime | |
90 if age > 10: | |
91 raise ValueError("poll data is stale. age=%s" % age) | |
92 | |
93 for dev in self.settings.poller.lastAddrs: | |
94 if not dev.get('signal'): | |
95 continue | |
96 uri = URIRef("http://bigasterisk.com/wifiDevice/%s" % dev['mac']) | |
97 g.add((uri, ROOM['macAddress'], Literal(dev['mac']))) | |
98 g.add((uri, ROOM['connected'], aps)) | |
99 if 'rawName' in dev: | |
100 g.add((uri, ROOM['wifiNetworkName'], Literal(dev['rawName']))) | |
101 g.add((uri, ROOM['deviceName'], Literal(dev['name']))) | |
102 g.add((uri, ROOM['signalStrength'], Literal(dev['signal']))) | |
103 | |
104 self.set_header('Content-type', 'application/x-trig') | |
105 self.write(g.asTrig()) | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
106 |
805 | 107 class Application(cyclone.web.Application): |
108 def __init__(self, wifi, poller): | |
109 handlers = [ | |
110 (r"/", Index), | |
111 (r'/json', Json), | |
112 (r'/graph', GraphHandler), | |
113 (r'/table', Table), | |
114 #(r'/activity', Activity), | |
115 ] | |
116 settings = { | |
117 'wifi' : wifi, | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
118 'poller' : poller, |
805 | 119 'mongo' : Connection('bang', 27017, |
120 tz_aware=True)['house']['sensor'] | |
121 } | |
122 cyclone.web.Application.__init__(self, handlers, **settings) | |
123 | |
124 class Poller(object): | |
125 def __init__(self, wifi, mongo): | |
126 self.wifi = wifi | |
127 self.mongo = mongo | |
128 self.lastAddrs = [] | |
129 self.lastWithSignal = [] | |
130 self.lastPollTime = 0 | |
131 | |
132 def assertCurrent(self): | |
133 dt = time.time() - self.lastPollTime | |
134 assert dt < 10, "last poll was %s sec ago" % dt | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
135 |
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
136 @inlineCallbacks |
805 | 137 def poll(self): |
138 try: | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
139 newAddrs = yield self.wifi.getPresentMacAddrs() |
805 | 140 |
141 newWithSignal = [a for a in newAddrs if a.get('signal')] | |
142 | |
143 actions = self.computeActions(newWithSignal) | |
144 for action in actions: | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
145 log.info("action: %s", action) |
805 | 146 action['created'] = datetime.datetime.now(tz.gettz('UTC')) |
147 mongo.save(action) | |
148 try: | |
149 self.doEntranceMusic(action) | |
150 except Exception, e: | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
151 log.error("entrancemusic error: %r", e) |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
152 |
805 | 153 self.lastWithSignal = newWithSignal |
855 | 154 if actions: # this doesn't currently include signal strength changes |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
155 fetch(reasoning + "immediateUpdate", |
857
1168b0cd90af
fix fetch calls in tomatowifi
drewp <drewp@bigasterisk.com>
parents:
856
diff
changeset
|
156 timeout=2, |
1168b0cd90af
fix fetch calls in tomatowifi
drewp <drewp@bigasterisk.com>
parents:
856
diff
changeset
|
157 headers={'user-agent': ['tomatoWifi']}).addErrback(log.warn) |
805 | 158 self.lastAddrs = newAddrs |
159 self.lastPollTime = time.time() | |
160 except Exception, e: | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
161 log.error("poll error: %s\n%s", e, traceback.format_exc()) |
805 | 162 |
163 def computeActions(self, newWithSignal): | |
164 def removeVolatile(a): | |
165 ret = dict((k,v) for k,v in a.items() if k in ['name', 'mac']) | |
166 ret['signal'] = bool(a.get('signal')) | |
167 return ret | |
168 | |
169 def find(a, others): | |
170 a = removeVolatile(a) | |
171 return any(a == removeVolatile(o) for o in others) | |
172 | |
173 actions = [] | |
174 | |
175 def makeAction(addr, act): | |
176 return dict(sensor="wifi", | |
177 address=addr.get('mac'), | |
178 name=addr.get('name'), | |
179 networkName=addr.get('rawName'), | |
180 action=act) | |
181 | |
182 for addr in newWithSignal: | |
183 if not find(addr, self.lastWithSignal): | |
184 # the point of all the removeVolatile stuff is so | |
185 # I have the complete addr object here, although | |
186 # it is currently mostly thrown out by makeAction | |
187 actions.append(makeAction(addr, 'arrive')) | |
188 | |
189 for addr in self.lastWithSignal: | |
190 if not find(addr, newWithSignal): | |
191 actions.append(makeAction(addr, 'leave')) | |
192 | |
193 return actions | |
194 | |
195 | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
196 # these need to move out to their own service |
805 | 197 def doEntranceMusic(self, action): |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
198 import restkit, jsonlib |
805 | 199 dt = self.deltaSinceLastArrive(action['name']) |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
200 log.debug("dt=%s", dt) |
805 | 201 if dt > datetime.timedelta(hours=1): |
202 hub = restkit.Resource( | |
203 # PSHB not working yet; "http://bang:9030/" | |
204 "http://slash:9049/" | |
205 ) | |
206 action = action.copy() | |
207 del action['created'] | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
208 del action['_id'] |
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
209 log.info("post to %s", hub) |
805 | 210 hub.post("visitorNet", payload=jsonlib.dumps(action)) |
211 | |
212 def deltaSinceLastArrive(self, name): | |
213 results = list(self.mongo.find({'name' : name}).sort('created', | |
214 DESCENDING).limit(1)) | |
215 if not results: | |
216 return datetime.timedelta.max | |
217 now = datetime.datetime.now(tz.gettz('UTC')) | |
218 last = results[0]['created'].replace(tzinfo=tz.gettz('UTC')) | |
219 return now - last | |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
220 |
805 | 221 |
222 if __name__ == '__main__': | |
223 config = { | |
224 'servePort' : 9070, | |
225 'pollFrequency' : 1/5, | |
226 } | |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
227 from twisted.python import log as twlog |
805 | 228 #log.startLogging(sys.stdout) |
806
85e50a597244
entrancemusic improve logging
drewp <drewp@bigasterisk.com>
parents:
805
diff
changeset
|
229 #log.setLevel(10) |
856
4b386fc51325
rewrite tomatowifi from restkit to cyclone httpclient
drewp <drewp@bigasterisk.com>
parents:
855
diff
changeset
|
230 log.setLevel(logging.DEBUG) |
805 | 231 |
232 mongo = Connection('bang', 27017)['visitor']['visitor'] | |
233 | |
234 wifi = Wifi() | |
235 poller = Poller(wifi, mongo) | |
236 task.LoopingCall(poller.poll).start(1/config['pollFrequency']) | |
237 | |
238 reactor.listenTCP(config['servePort'], Application(wifi, poller)) | |
239 reactor.run() |