diff service/tomatoWifi/tomatoWifi.py @ 867:d9bc9a82dcca

redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table Ignore-this: 18bade72e14d40532bd019791d03fa7d darcs-hash:20130407000819-312f9-6b262edf71421b17947050e80560005d7bffa80b
author drewp <drewp@bigasterisk.com>
date Sat, 06 Apr 2013 17:08:19 -0700
parents 1168b0cd90af
children bca6d6c63bdc
line wrap: on
line diff
--- a/service/tomatoWifi/tomatoWifi.py	Sun Feb 10 13:41:35 2013 -0800
+++ b/service/tomatoWifi/tomatoWifi.py	Sat Apr 06 17:08:19 2013 -0700
@@ -12,18 +12,19 @@
 """
 from __future__ import division
 import sys, cyclone.web, json, traceback, time, pystache, datetime, logging
+import web.utils
 from cyclone.httpclient import fetch
 sys.path.append("/home/drewp/projects/photo/lib/python2.7/site-packages")
 from dateutil import tz
 from twisted.internet import reactor, task
-from twisted.internet.defer import inlineCallbacks, returnValue
-
+from twisted.internet.defer import inlineCallbacks
 
 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
+from dhcpparse import addDhcpData
 
 sys.path.append("/my/proj/homeauto/lib")
 from cycloneerr import PrettyErrorHandler
@@ -53,21 +54,44 @@
 
 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
+        def rowDict(row):
+            row['cls'] = "signal" if row.get('connected') else "nosignal"
+            if 'name' not in row:
+                row['name'] = row['clientHostname']
+            if 'signal' not in row:
+                row['signal'] = 'yes' if row['connected'] else 'no'
+
+            try:
+                conn = self.whenConnected(row['mac'])
+                row['connectedAgo'] = web.utils.datestr(
+                    conn.astimezone(tz.tzutc()).replace(tzinfo=None))
+            except ValueError:
+                pass
+            
+            return row
 
         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'))))))
+                            key=lambda a: (not a.get('connected'),
+                                           a.get('name'))))))
 
+    def whenConnected(self, macThatIsNowConnected):
+        lastArrive = None
+        for ev in self.settings.mongo.find({'address': macThatIsNowConnected},
+                                           sort=[('created', -1)],
+                                           max_scan=100000):
+            if ev['action'] == 'arrive':
+                lastArrive = ev
+            if ev['action'] == 'leave':
+                break
+        if lastArrive is None:
+            raise ValueError("no past arrivals")
+        
+        return lastArrive['created']
+        
 
 class Json(PrettyErrorHandler, cyclone.web.RequestHandler):
     def get(self):
@@ -91,36 +115,22 @@
             raise ValueError("poll data is stale. age=%s" % age)
 
         for dev in self.settings.poller.lastAddrs:
-            if not dev.get('signal'):
+            if not dev.get('connected'):
                 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'])))
+            if 'clientHostname' in dev:
+                g.add((uri, ROOM['wifiNetworkName'], Literal(dev['clientHostname'])))
+            if 'name' in dev:
+                g.add((uri, ROOM['deviceName'], Literal(dev['name'])))
+            if 'signal' in dev:
+                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
@@ -137,8 +147,9 @@
     def poll(self):
         try:
             newAddrs = yield self.wifi.getPresentMacAddrs()
-
-            newWithSignal = [a for a in newAddrs if a.get('signal')]
+            addDhcpData(newAddrs)
+            
+            newWithSignal = [a for a in newAddrs if a.get('connected')]
 
             actions = self.computeActions(newWithSignal)
             for action in actions:
@@ -161,33 +172,27 @@
             log.error("poll error: %s\n%s", e, traceback.format_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",
+            d = dict(sensor="wifi",
                         address=addr.get('mac'),
                         name=addr.get('name'),
-                        networkName=addr.get('rawName'),
+                        networkName=addr.get('clientHostname'),
                         action=act)
+            if act == 'arrive' and 'ip' in addr:
+                # this won't cover the possible case that you get on
+                # wifi but don't have an ip yet. We'll record an
+                # action with no ip and then never record your ip.
+                d['ip'] = addr['ip']
+            return d                             
 
         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
+            if addr['mac'] not in [r['mac'] for r in self.lastWithSignal]:
                 actions.append(makeAction(addr, 'arrive'))
 
         for addr in self.lastWithSignal:
-            if not find(addr, newWithSignal):
+            if addr['mac'] not in [r['mac'] for r in newWithSignal]:
                 actions.append(makeAction(addr, 'leave'))
 
         return actions
@@ -227,13 +232,24 @@
     from twisted.python import log as twlog
     #log.startLogging(sys.stdout)
     #log.setLevel(10)
-    log.setLevel(logging.DEBUG)
+    #log.setLevel(logging.DEBUG)
 
-    mongo = Connection('bang', 27017)['visitor']['visitor']
+    mongo = Connection('bang', 27017, tz_aware=True)['visitor']['visitor']
 
     wifi = Wifi()
     poller = Poller(wifi, mongo)
     task.LoopingCall(poller.poll).start(1/config['pollFrequency'])
 
-    reactor.listenTCP(config['servePort'], Application(wifi, poller))
+    reactor.listenTCP(config['servePort'],
+                      cyclone.web.Application(
+                          [
+                              (r"/", Index),
+                              (r'/json', Json),
+                              (r'/graph', GraphHandler),
+                              (r'/table', Table),
+                              #(r'/activity', Activity),
+                          ],
+                          wifi=wifi,
+                          poller=poller,
+                          mongo=mongo))
     reactor.run()