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