Mercurial > code > home > repos > homeauto
annotate service/wifi/wifi.py @ 1705:250f4c27d56f
less logging
author | drewp@bigasterisk.com |
---|---|
date | Sat, 23 Oct 2021 13:21:06 -0700 |
parents | f88ff1021ee0 |
children | 81aa0873b48d |
rev | line source |
---|---|
0 | 1 """ |
2 scrape the tomato router status pages to see who's connected to the | |
3 wifi access points. Includes leases that aren't currently connected. | |
4 | |
5 Returns: | |
6 json listing (for magma page) | |
7 rdf graph (for reasoning) | |
8 activity stream, when we start saving history | |
9 | |
10 Todo: this should be the one polling and writing to mongo, not entrancemusic | |
162 | 11 |
0 | 12 """ |
1679 | 13 from collections import defaultdict |
14 import datetime | |
15 import json | |
16 import logging | |
17 import sys | |
18 import time | |
19 import traceback | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
20 from typing import List |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
21 |
1679 | 22 import ago |
51
d2842eedd56d
rewrite tomatowifi from restkit to cyclone httpclient
drewp@bigasterisk.com
parents:
50
diff
changeset
|
23 from cyclone.httpclient import fetch |
1679 | 24 import cyclone.web |
25 from cycloneerr import PrettyErrorHandler | |
0 | 26 from dateutil import tz |
1679 | 27 import docopt |
28 from patchablegraph import ( | |
29 CycloneGraphEventsHandler, | |
30 CycloneGraphHandler, | |
31 PatchableGraph, | |
32 ) | |
33 from prometheus_client import Counter, Gauge, Summary | |
34 from prometheus_client.exposition import generate_latest | |
35 from prometheus_client.registry import REGISTRY | |
36 from pymongo import DESCENDING, MongoClient as Connection | |
37 from pymongo.collection import Collection | |
38 import pystache | |
39 from rdflib import ConjunctiveGraph, Literal, Namespace, RDF | |
40 from standardservice.logsetup import log | |
0 | 41 from twisted.internet import reactor, task |
1679 | 42 from twisted.internet.defer import ensureDeferred, inlineCallbacks |
383
8f5a16a55f64
various docker setups and build fixes
drewp@bigasterisk.com
parents:
340
diff
changeset
|
43 |
1679 | 44 from scrape import SeenNode, Wifi |
36 | 45 |
422 | 46 AST = Namespace("http://bigasterisk.com/") |
0 | 47 DEV = Namespace("http://projects.bigasterisk.com/device/") |
48 ROOM = Namespace("http://projects.bigasterisk.com/room/") | |
1 | 49 |
1679 | 50 |
0 | 51 class Index(PrettyErrorHandler, cyclone.web.RequestHandler): |
1679 | 52 |
0 | 53 def get(self): |
54 age = time.time() - self.settings.poller.lastPollTime | |
55 if age > 10: | |
56 raise ValueError("poll data is stale. age=%s" % age) | |
659 | 57 |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
58 self.set_header("Content-Type", "text/html") |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
59 self.write(open("index.html").read()) |
51
d2842eedd56d
rewrite tomatowifi from restkit to cyclone httpclient
drewp@bigasterisk.com
parents:
50
diff
changeset
|
60 |
1679 | 61 |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
62 def whenConnected(mongo, macThatIsNowConnected): |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
63 lastArrive = None |
1679 | 64 for ev in mongo.find({'address': macThatIsNowConnected.upper()}, sort=[('created', -1)], max_time_ms=5000): |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
65 if ev['action'] == 'arrive': |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
66 lastArrive = ev |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
67 if ev['action'] == 'leave': |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
68 break |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
69 if lastArrive is None: |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
70 raise ValueError("no past arrivals") |
0 | 71 |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
72 return lastArrive['created'] |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
73 |
1679 | 74 |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
75 def connectedAgoString(conn): |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
76 return ago.human(conn.astimezone(tz.tzutc()).replace(tzinfo=None)) |
659 | 77 |
1679 | 78 |
0 | 79 class Table(PrettyErrorHandler, cyclone.web.RequestHandler): |
1679 | 80 |
0 | 81 def get(self): |
1679 | 82 |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
83 def rowDict(row): |
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
84 row['cls'] = "signal" if row.get('connected') else "nosignal" |
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
85 if 'name' not in row: |
74
bca6d6c63bdc
handle wifi users with no clientHostname
drewp@bigasterisk.com
parents:
62
diff
changeset
|
86 row['name'] = row.get('clientHostname', '-') |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
87 if 'signal' not in row: |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
88 row['signal'] = 'yes' if row.get('connected') else 'no' |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
89 |
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
90 try: |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
91 conn = whenConnected(self.settings.mongo, row.get('mac', '??')) |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
92 row['connectedAgo'] = connectedAgoString(conn) |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
93 except ValueError: |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
94 row['connectedAgo'] = 'yes' if row.get('connected') else '' |
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
95 row['router'] = row.get('ssid', '') |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
96 return row |
0 | 97 |
98 self.set_header("Content-Type", "application/xhtml+xml") | |
1679 | 99 self.write( |
100 pystache.render( | |
101 open("table.mustache").read(), | |
102 dict(rows=sorted(map(rowDict, self.settings.poller.lastAddrs), | |
103 key=lambda a: (not a.get('connected'), a.get('name')))))) | |
104 | |
51
d2842eedd56d
rewrite tomatowifi from restkit to cyclone httpclient
drewp@bigasterisk.com
parents:
50
diff
changeset
|
105 |
0 | 106 class Json(PrettyErrorHandler, cyclone.web.RequestHandler): |
1679 | 107 |
0 | 108 def get(self): |
109 self.set_header("Content-Type", "application/json") | |
110 age = time.time() - self.settings.poller.lastPollTime | |
111 if age > 10: | |
112 raise ValueError("poll data is stale. age=%s" % age) | |
1679 | 113 self.write(json.dumps({"wifi": self.settings.poller.lastAddrs, "dataAge": age})) |
114 | |
115 | |
116 POLL = Summary('poll', 'Time in HTTP poll requests') | |
117 POLL_SUCCESSES = Counter('poll_successes', 'poll success count') | |
118 POLL_ERRORS = Counter('poll_errors', 'poll error count') | |
119 CURRENTLY_ON_WIFI = Gauge('currently_on_wifi', 'current nodes known to wifi router (some may be wired)') | |
120 MAC_ON_WIFI = Gauge('connected', 'mac addr is currently connected', ['mac']) | |
121 | |
0 | 122 |
123 class Poller(object): | |
1679 | 124 |
125 def __init__(self, wifi: Wifi, mongo: Collection): | |
0 | 126 self.wifi = wifi |
127 self.mongo = mongo | |
1679 | 128 self.lastAddrs = [] # List[SeenNode] |
0 | 129 self.lastWithSignal = [] |
130 self.lastPollTime = 0 | |
131 | |
1679 | 132 @POLL.time() |
133 async def poll(self): | |
0 | 134 try: |
1679 | 135 newAddrs = await self.wifi.getPresentMacAddrs() |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
136 self.onNodes(newAddrs) |
1679 | 137 POLL_SUCCESSES.inc() |
421 | 138 except Exception as e: |
175
c81a451f9b26
rewrites for better graph export, removal of dhcp reader
drewp@bigasterisk.com
parents:
162
diff
changeset
|
139 log.error("poll error: %r\n%s", e, traceback.format_exc()) |
1679 | 140 POLL_ERRORS.inc() |
0 | 141 |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
142 def onNodes(self, newAddrs: List[SeenNode]): |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
143 now = int(time.time()) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
144 newWithSignal = [a for a in newAddrs if a.connected] |
1679 | 145 CURRENTLY_ON_WIFI.set(len(newWithSignal)) |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
146 |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
147 actions = self.computeActions(newWithSignal) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
148 for action in actions: |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
149 log.info("action: %s", action) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
150 action['created'] = datetime.datetime.now(tz.gettz('UTC')) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
151 mongo.save(action) |
1679 | 152 MAC_ON_WIFI.labels(mac=action['address'].lower()).set(1 if action['action'] == 'arrive' else 0) |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
153 if now // 3600 > self.lastPollTime // 3600: |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
154 log.info('hourly writes') |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
155 for addr in newWithSignal: |
1679 | 156 MAC_ON_WIFI.labels(mac=addr.mac.lower()).set(1) |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
157 |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
158 self.lastWithSignal = newWithSignal |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
159 self.lastAddrs = newAddrs |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
160 self.lastPollTime = now |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
161 |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
162 self.updateGraph(masterGraph) |
566
c3d06c350e24
no more immediateUpdate since we push patch events now. and the code was broken for py3 anyway
drewp@bigasterisk.com
parents:
564
diff
changeset
|
163 |
0 | 164 def computeActions(self, newWithSignal): |
165 actions = [] | |
166 | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
167 def makeAction(addr: SeenNode, act: str): |
1679 | 168 d = dict( |
169 sensor="wifi", | |
170 address=addr.mac.upper(), # mongo data is legacy uppercase | |
171 action=act) | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
172 if act == 'arrive': |
62
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
173 # this won't cover the possible case that you get on |
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
174 # wifi but don't have an ip yet. We'll record an |
f8cc3d1baa85
redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table
drewp@bigasterisk.com
parents:
52
diff
changeset
|
175 # action with no ip and then never record your ip. |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
176 d['ip'] = addr.ip |
659 | 177 return d |
0 | 178 |
179 for addr in newWithSignal: | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
180 if addr.mac not in [r.mac for r in self.lastWithSignal]: |
0 | 181 actions.append(makeAction(addr, 'arrive')) |
182 | |
183 for addr in self.lastWithSignal: | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
184 if addr.mac not in [r.mac for r in newWithSignal]: |
0 | 185 actions.append(makeAction(addr, 'leave')) |
186 | |
187 return actions | |
188 | |
189 def deltaSinceLastArrive(self, name): | |
1679 | 190 results = list(self.mongo.find({'name': name}).sort('created', DESCENDING).limit(1)) |
0 | 191 if not results: |
192 return datetime.timedelta.max | |
193 now = datetime.datetime.now(tz.gettz('UTC')) | |
194 last = results[0]['created'].replace(tzinfo=tz.gettz('UTC')) | |
195 return now - last | |
51
d2842eedd56d
rewrite tomatowifi from restkit to cyclone httpclient
drewp@bigasterisk.com
parents:
50
diff
changeset
|
196 |
340 | 197 def updateGraph(self, masterGraph): |
198 g = ConjunctiveGraph() | |
199 ctx = DEV['wifi'] | |
200 | |
201 # someday i may also record specific AP and their strength, | |
202 # for positioning. But many users just want to know that the | |
203 # device is connected to some bigasterisk AP. | |
204 age = time.time() - self.lastPollTime | |
205 if age > 10: | |
206 raise ValueError("poll data is stale. age=%s" % age) | |
207 | |
208 for dev in self.lastAddrs: | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
209 if not dev.connected: |
340 | 210 continue |
427
db031d9ec28e
don't use 'connected' for time and for network. add rdf:type.
drewp@bigasterisk.com
parents:
423
diff
changeset
|
211 g.add((dev.uri, RDF.type, ROOM['NetworkedDevice'], ctx)) |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
212 g.add((dev.uri, ROOM['macAddress'], Literal(dev.mac), ctx)) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
213 g.add((dev.uri, ROOM['ipAddress'], Literal(dev.ip), ctx)) |
340 | 214 |
1679 | 215 for s, p, o in dev.stmts: |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
216 g.add((s, p, o, ctx)) |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
217 |
340 | 218 try: |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
219 conn = whenConnected(mongo, dev.mac) |
340 | 220 except ValueError: |
221 traceback.print_exc() | |
222 pass | |
223 else: | |
1679 | 224 g.add((dev.uri, ROOM['connectedAgo'], Literal(connectedAgoString(conn)), ctx)) |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
225 g.add((dev.uri, ROOM['connected'], Literal(conn), ctx)) |
340 | 226 masterGraph.setToGraph(g) |
227 | |
1679 | 228 |
659 | 229 class RemoteSuspend(PrettyErrorHandler, cyclone.web.RequestHandler): |
1679 | 230 |
659 | 231 def post(self): |
232 # windows is running shutter (https://www.den4b.com/products/shutter) | |
233 fetch('http://DESKTOP-GOU4AC4:8011/action', postdata={'id': 'Sleep'}) | |
234 | |
0 | 235 |
1679 | 236 class Metrics(cyclone.web.RequestHandler): |
237 | |
238 def get(self): | |
239 self.add_header('content-type', 'text/plain') | |
240 self.write(generate_latest(REGISTRY)) | |
241 | |
242 | |
0 | 243 if __name__ == '__main__': |
189 | 244 args = docopt.docopt(''' |
245 Usage: | |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
246 wifi.py [options] |
189 | 247 |
248 Options: | |
249 -v, --verbose more logging | |
250 --port=<n> serve on port [default: 9070] | |
251 --poll=<freq> poll frequency [default: .2] | |
252 ''') | |
253 if args['--verbose']: | |
254 from twisted.python import log as twlog | |
255 twlog.startLogging(sys.stdout) | |
256 log.setLevel(10) | |
257 log.setLevel(logging.DEBUG) | |
0 | 258 |
1679 | 259 mongo = Connection('mongodb.default.svc.cluster.local', 27017, tz_aware=True)['visitor']['visitor'] |
0 | 260 |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
261 config = ConjunctiveGraph() |
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
262 config.parse(open('private_config.n3'), format='n3') |
659 | 263 |
340 | 264 masterGraph = PatchableGraph() |
423
e0703c7824e9
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
drewp@bigasterisk.com
parents:
422
diff
changeset
|
265 wifi = Wifi(config) |
0 | 266 poller = Poller(wifi, mongo) |
1679 | 267 task.LoopingCall(lambda: ensureDeferred(poller.poll())).start(1 / float(args['--poll'])) |
0 | 268 |
422 | 269 reactor.listenTCP( |
270 int(args['--port']), | |
271 cyclone.web.Application( | |
272 [ | |
273 (r"/", Index), | |
1679 | 274 (r"/build/(bundle\.js)", cyclone.web.StaticFileHandler, { |
275 "path": 'build' | |
276 }), | |
422 | 277 (r'/json', Json), |
1679 | 278 (r'/graph/wifi', CycloneGraphHandler, { |
279 'masterGraph': masterGraph | |
280 }), | |
281 (r'/graph/wifi/events', CycloneGraphEventsHandler, { | |
282 'masterGraph': masterGraph | |
283 }), | |
422 | 284 (r'/table', Table), |
659 | 285 (r'/remoteSuspend', RemoteSuspend), |
1679 | 286 (r'/metrics', Metrics), |
422 | 287 #(r'/activity', Activity), |
288 ], | |
289 wifi=wifi, | |
290 poller=poller, | |
291 mongo=mongo)) | |
0 | 292 reactor.run() |