Mercurial > code > home > repos > homeauto
diff service/tomatoWifi/tomatoWifi.py @ 0:6fd208b97616
start
Ignore-this: e06ac598970a0d4750f588ab89f56996
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Mon, 01 Aug 2011 03:30:30 -0700 |
parents | |
children | 26a6cf58743d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/tomatoWifi/tomatoWifi.py Mon Aug 01 03:30:30 2011 -0700 @@ -0,0 +1,215 @@ +#!/usr/bin/python +""" +scrape the tomato router status pages to see who's connected to the +wifi access points. Includes leases that aren't currently connected. + +Returns: + json listing (for magma page) + rdf graph (for reasoning) + activity stream, when we start saving history + +Todo: this should be the one polling and writing to mongo, not entrancemusic +""" +from __future__ import division +import sys, cyclone.web, simplejson, traceback, time, pystache, datetime +from dateutil import tz +from twisted.python import log +from twisted.internet import reactor, task + +from pymongo import Connection, DESCENDING +from rdflib import Namespace, Literal, URIRef +sys.path.append("/my/site/magma") +from stategraph import StateGraph +from wifi import Wifi + +sys.path.append("/my/proj/homeauto/lib") +from cycloneerr import PrettyErrorHandler + +DEV = Namespace("http://projects.bigasterisk.com/device/") +ROOM = Namespace("http://projects.bigasterisk.com/room/") + +class Index(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + + age = time.time() - self.settings.poller.lastPollTime + if age > 10: + raise ValueError("poll data is stale. age=%s" % age) + + self.write("this is wifiusage. needs index page that embeds the table") + +class Table(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + def rowDict(addr): + addr['cls'] = "signal" if addr.get('signal') else "nosignal" + if 'lease' in addr: + addr['lease'] = addr['lease'].replace("0 days, ", "") + return addr + + self.set_header("Content-Type", "application/xhtml+xml") + self.write(pystache.render( + open("table.mustache").read(), + dict( + rows=sorted(map(rowDict, self.settings.poller.lastAddrs), + key=lambda a: (a.get('router'), + a.get('name'), + a.get('mac')))))) + + +class Json(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + self.set_header("Content-Type", "application/json") + age = time.time() - self.settings.poller.lastPollTime + if age > 10: + raise ValueError("poll data is stale. age=%s" % age) + self.write(simplejson.dumps({"wifi" : self.settings.poller.lastAddrs, + "dataAge" : age})) + +class GraphHandler(PrettyErrorHandler, cyclone.web.RequestHandler): + def get(self): + g = StateGraph(ctx=DEV['wifi']) + + # someday i may also record specific AP and their strength, + # for positioning. But many users just want to know that the + # device is connected to some bigasterisk AP. + aps = URIRef("http://bigasterisk.com/wifiAccessPoints") + age = time.time() - self.settings.poller.lastPollTime + if age > 10: + raise ValueError("poll data is stale. age=%s" % age) + + for dev in self.settings.poller.lastAddrs: + if not dev.get('signal'): + continue + uri = URIRef("http://bigasterisk.com/wifiDevice/%s" % dev['mac']) + g.add((uri, ROOM['macAddress'], Literal(dev['mac']))) + g.add((uri, ROOM['connected'], aps)) + if 'rawName' in dev: + g.add((uri, ROOM['wifiNetworkName'], Literal(dev['rawName']))) + g.add((uri, ROOM['deviceName'], Literal(dev['name']))) + g.add((uri, ROOM['signalStrength'], Literal(dev['signal']))) + + self.set_header('Content-type', 'application/x-trig') + self.write(g.asTrig()) + +class Application(cyclone.web.Application): + def __init__(self, wifi, poller): + handlers = [ + (r"/", Index), + (r'/json', Json), + (r'/graph', GraphHandler), + (r'/table', Table), + #(r'/activity', Activity), + ] + settings = { + 'wifi' : wifi, + 'poller' : poller, + 'mongo' : Connection('bang', 27017, + tz_aware=True)['house']['sensor'] + } + cyclone.web.Application.__init__(self, handlers, **settings) + +class Poller(object): + def __init__(self, wifi, mongo): + self.wifi = wifi + self.mongo = mongo + self.lastAddrs = [] + self.lastWithSignal = [] + self.lastPollTime = 0 + + def assertCurrent(self): + dt = time.time() - self.lastPollTime + assert dt < 10, "last poll was %s sec ago" % dt + + def poll(self): + try: + newAddrs = self.wifi.getPresentMacAddrs() + + newWithSignal = [a for a in newAddrs if a.get('signal')] + + actions = self.computeActions(newWithSignal) + for action in actions: + action['created'] = datetime.datetime.now(tz.gettz('UTC')) + mongo.save(action) + try: + self.doEntranceMusic(action) + except Exception, e: + print "entrancemusic error", e + + self.lastWithSignal = newWithSignal + self.lastAddrs = newAddrs + self.lastPollTime = time.time() + except Exception, e: + print "poll error", e + traceback.print_exc() + + def computeActions(self, newWithSignal): + def removeVolatile(a): + ret = dict((k,v) for k,v in a.items() if k in ['name', 'mac']) + ret['signal'] = bool(a.get('signal')) + return ret + + def find(a, others): + a = removeVolatile(a) + return any(a == removeVolatile(o) for o in others) + + actions = [] + + def makeAction(addr, act): + return dict(sensor="wifi", + address=addr.get('mac'), + name=addr.get('name'), + networkName=addr.get('rawName'), + action=act) + + for addr in newWithSignal: + if not find(addr, self.lastWithSignal): + # the point of all the removeVolatile stuff is so + # I have the complete addr object here, although + # it is currently mostly thrown out by makeAction + actions.append(makeAction(addr, 'arrive')) + + for addr in self.lastWithSignal: + if not find(addr, newWithSignal): + actions.append(makeAction(addr, 'leave')) + + return actions + + + def doEntranceMusic(self, action): + # these need to move out to their own service + dt = self.deltaSinceLastArrive(action['name']) + if dt > datetime.timedelta(hours=1): + import restkit, jsonlib + hub = restkit.Resource( + # PSHB not working yet; "http://bang:9030/" + "http://slash:9049/" + ) + action = action.copy() + del action['created'] + hub.post("visitorNet", payload=jsonlib.dumps(action)) + + def deltaSinceLastArrive(self, name): + results = list(self.mongo.find({'name' : name}).sort('created', + DESCENDING).limit(1)) + if not results: + return datetime.timedelta.max + now = datetime.datetime.now(tz.gettz('UTC')) + last = results[0]['created'].replace(tzinfo=tz.gettz('UTC')) + return now - last + + +if __name__ == '__main__': + config = { + 'servePort' : 9070, + 'pollFrequency' : 1/5, + } + #log.startLogging(sys.stdout) + + + mongo = Connection('bang', 27017)['visitor']['visitor'] + + wifi = Wifi() + poller = Poller(wifi, mongo) + task.LoopingCall(poller.poll).start(1/config['pollFrequency']) + + reactor.listenTCP(config['servePort'], Application(wifi, poller)) + reactor.run()