# HG changeset patch
# User drewp@bigasterisk.com
# Date 1430698880 25200
# Node ID c81a451f9b26224d2424a56690eaf5e06a02ae82
# Parent ab98f8d72be07fb8e2d2fe45da2442131b855321
rewrites for better graph export, removal of dhcp reader
Ignore-this: ecc5280b15d66020412f82ad84862074
diff -r ab98f8d72be0 -r c81a451f9b26 service/tomatoWifi/dhcpparse.py
--- a/service/tomatoWifi/dhcpparse.py Sat May 02 18:52:15 2015 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-def addDhcpData(rows):
- """given dicts with 'mac', add other data from the dhcp cache"""
- currentLease = None
- for line in open('/var/lib/dhcp/dhcpd.leases'):
- if line.startswith('lease '):
- currentLease = {'ip': line.split()[1]}
- elif line.startswith(' hardware ethernet '):
- currentLease['mac'] = line.split()[2].strip(';').upper()
- elif line.startswith(' client-hostname'):
- currentLease['clientHostname'] = line.split(None, 2)[1].strip('";')
- elif line.startswith(' binding state'):
- currentLease['bindingState'] = line.split()[2].strip(';')
- elif line.startswith('}'):
- if currentLease.get('bindingState') == 'active':
- # there seem to be a lot of 'active' blocks
- # for the same ip addr and mac. I haven't
- # looked into whether they hold different
- # lease terms or what
- for r in rows:
- if r['mac'] == currentLease['mac']:
- r.update(currentLease)
-
-
diff -r ab98f8d72be0 -r c81a451f9b26 service/tomatoWifi/index.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tomatoWifi/index.html Sun May 03 17:21:20 2015 -0700
@@ -0,0 +1,79 @@
+
+
+
+ dhcp leases
+
+
+
+
+
+
+
+
Devices on wifi
+
+
+
+
+
+
+
+
+
name
+
MAC
+
Connected
+
+
+
+
{{item.deviceName}}
+
{{item.mac}}
+
{{item.connectedAgo}}
+
+
+
+
+
+
+
+
+
+
diff -r ab98f8d72be0 -r c81a451f9b26 service/tomatoWifi/tomatoWifi.py
--- a/service/tomatoWifi/tomatoWifi.py Sat May 02 18:52:15 2015 -0700
+++ b/service/tomatoWifi/tomatoWifi.py Sun May 03 17:21:20 2015 -0700
@@ -25,20 +25,11 @@
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
from logsetup import log
-import rdflib
-from rdflib import plugin
-plugin.register(
- "sparql", rdflib.query.Processor,
- "rdfextras.sparql.processor", "Processor")
-plugin.register(
- "sparql", rdflib.query.Result,
- "rdfextras.sparql.query", "SPARQLQueryResult")
DEV = Namespace("http://projects.bigasterisk.com/device/")
ROOM = Namespace("http://projects.bigasterisk.com/room/")
@@ -50,9 +41,28 @@
age = time.time() - self.settings.poller.lastPollTime
if age > 10:
raise ValueError("poll data is stale. age=%s" % age)
+
+ self.set_header("Content-Type", "text/html")
+ self.write(open("index.html").read())
- self.write("this is wifiusage. needs index page that embeds the table")
+def whenConnected(mongo, macThatIsNowConnected):
+ lastArrive = None
+ for ev in mongo.find({'address': macThatIsNowConnected.upper()},
+ 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']
+
+def connectedAgoString(conn):
+ return web.utils.datestr(
+ conn.astimezone(tz.tzutc()).replace(tzinfo=None))
+
class Table(PrettyErrorHandler, cyclone.web.RequestHandler):
def get(self):
def rowDict(row):
@@ -60,15 +70,14 @@
if 'name' not in row:
row['name'] = row.get('clientHostname', '-')
if 'signal' not in row:
- row['signal'] = 'yes' if row['connected'] else 'no'
+ row['signal'] = 'yes' if row.get('connected') else 'no'
try:
- conn = self.whenConnected(row['mac'])
- row['connectedAgo'] = web.utils.datestr(
- conn.astimezone(tz.tzutc()).replace(tzinfo=None))
+ conn = whenConnected(self.settings.mongo, row.get('mac', '??'))
+ row['connectedAgo'] = connectedAgoString(conn)
except ValueError:
- pass
-
+ row['connectedAgo'] = 'yes' if row.get('connected') else ''
+ row['router'] = row.get('ssid', '')
return row
self.set_header("Content-Type", "application/xhtml+xml")
@@ -78,21 +87,7 @@
rows=sorted(map(rowDict, self.settings.poller.lastAddrs),
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):
@@ -128,6 +123,14 @@
g.add((uri, ROOM['deviceName'], Literal(dev['name'])))
if 'signal' in dev:
g.add((uri, ROOM['signalStrength'], Literal(dev['signal'])))
+ try:
+ conn = whenConnected(self.settings.mongo, dev['mac'])
+ except ValueError:
+ pass
+ else:
+ g.add((uri, ROOM['connectedAgo'],
+ Literal(connectedAgoString(conn))))
+ g.add((uri, ROOM['connected'], Literal(conn)))
self.set_header('Content-type', 'application/x-trig')
self.write(g.asTrig())
@@ -146,6 +149,12 @@
@inlineCallbacks
def poll(self):
+
+ connectedField = 'connected'
+
+ # UVA mode:
+ addDhcpData = lambda *args: None
+
try:
newAddrs = yield self.wifi.getPresentMacAddrs()
addDhcpData(newAddrs)
@@ -170,17 +179,17 @@
self.lastAddrs = newAddrs
self.lastPollTime = time.time()
except Exception, e:
- log.error("poll error: %s\n%s", e, traceback.format_exc())
+ log.error("poll error: %r\n%s", e, traceback.format_exc())
def computeActions(self, newWithSignal):
actions = []
def makeAction(addr, act):
d = dict(sensor="wifi",
- address=addr.get('mac'),
- name=addr.get('name'),
- networkName=addr.get('clientHostname'),
- action=act)
+ address=addr.get('mac').upper(), # mongo data is legacy uppercase
+ name=addr.get('name'),
+ 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
@@ -231,7 +240,7 @@
'pollFrequency' : 1/5,
}
from twisted.python import log as twlog
- #log.startLogging(sys.stdout)
+ #twlog.startLogging(sys.stdout)
#log.setLevel(10)
#log.setLevel(logging.DEBUG)
diff -r ab98f8d72be0 -r c81a451f9b26 service/tomatoWifi/wifi.py
--- a/service/tomatoWifi/wifi.py Sat May 02 18:52:15 2015 -0700
+++ b/service/tomatoWifi/wifi.py Sun May 03 17:21:20 2015 -0700
@@ -1,8 +1,8 @@
-import re, ast, logging, socket
+import re, ast, logging, socket, json
import lxml.html.soupparser
from twisted.internet.defer import inlineCallbacks, returnValue
from cyclone.httpclient import fetch
-from rdflib import Literal, Graph
+from rdflib import Literal, Graph, RDFS, URIRef
log = logging.getLogger()
@@ -13,32 +13,31 @@
class Wifi(object):
"""
gather the users of wifi from the tomato routers
-
- with host names from /var/lib/dhcp/dhcpd.leases
"""
- def __init__(self, tomatoConfig="/my/site/magma/tomato_config.js",
- accessN3="/my/proj/openid_proxy/access.n3"):
+ def __init__(self, accessN3="/my/proj/openid_proxy/access.n3"):
+ self.graph = Graph()
+ self.graph.parse('config.n3', format='n3')
- # ideally this would all be in the same rdf store, with int and
- # ext versions of urls
-
- txt = open(tomatoConfig).read().replace('\n', '')
- self.knownMacAddr = jsValue(txt, 'knownMacAddr')
- tomatoUrl = jsValue(txt, 'tomatoUrl')
-
+ #self._loadRouters(accessN3, tomatoUrl)
+
+ def _loadRouters(self, accessN3, tomatoUrl):
g = Graph()
g.parse(accessN3, format="n3")
- repl = {'/tomato1/' : None, '/tomato2/' : None}
+ repl = {
+ '/wifiRouter1/' : None,
+ #'/tomato2/' : None
+ }
for k in repl:
rows = list(g.query('''
PREFIX p:
SELECT ?prefix WHERE {
- [
+ ?site
p:requestPrefix ?public;
p:proxyUrlPrefix ?prefix
- ]
+ .
}''', initBindings={"public" : Literal(k)}))
repl[k] = str(rows[0][0])
+ log.debug('repl %r', repl)
self.routers = []
for url in tomatoUrl:
@@ -51,12 +50,24 @@
userPass, tail = tail.split("@", 1)
r.url = http + '//' + tail
r.headers = {'Authorization': ['Basic %s' % userPass.encode('base64').strip()]}
- r.name = {'tomato1' : 'bigasterisk5',
+ r.name = {'wifiRouter1' : 'bigasterisk5',
'tomato2' : 'bigasterisk4'}[name.split('/')[1]]
self.routers.append(r)
@inlineCallbacks
def getPresentMacAddrs(self):
+ rows = yield loadUvaData()
+ for row in rows:
+ if 'clientHostname' in row:
+ row['name'] = row['clientHostname']
+ mac = URIRef('http://bigasterisk.com/mac/%s' % row['mac'].lower())
+ label = self.graph.value(mac, RDFS.label)
+ if label:
+ row['name'] = label
+ returnValue(rows)
+
+ @inlineCallbacks
+ def getPresentMacAddrs_multirouter(self):
rows = []
for router in self.routers:
@@ -70,10 +81,10 @@
data = resp.body
if 'Wireless -- Authenticated Stations' in data:
# zyxel 'Station Info' page
- rows.extend(self.parseZyxel(data, router.name))
+ rows.extend(self._parseZyxel(data, router.name))
else:
# tomato page
- rows.extend(self.parseTomato(data, router.name))
+ rows.extend(self._parseTomato(data, router.name))
for r in rows:
try:
@@ -83,7 +94,7 @@
returnValue(rows)
- def parseZyxel(self, data, routerName):
+ def _parseZyxel(self, data, routerName):
root = lxml.html.soupparser.fromstring(data)
for tr in root.cssselect('tr'):
mac, assoc, uth, ssid, iface = [td.text_content().strip() for td in tr.getchildren()]
@@ -92,10 +103,35 @@
assoc = assoc.lower() == 'yes'
yield dict(router=routerName, mac=mac, assoc=assoc, connected=assoc)
- def parseTomato(self, data, routerName):
+ def _parseTomato(self, data, routerName):
for iface, mac, signal in jsValue(data, 'wldev'):
yield dict(router=routerName, mac=mac, signal=signal, connected=bool(signal))
+
+
+@inlineCallbacks
+def loadUvaData():
+ config = json.load(open("/my/proj/homeauto/service/tomatoWifi/priv-uva.json"))
+ headers = {'Authorization': ['Basic %s' % config['userPass'].encode('base64').strip()]}
+ resp = yield fetch('http://10.2.0.2/wlstationlist.cmd', headers=headers)
+ root = lxml.html.soupparser.fromstring(resp.body)
+ byMac = {}
+ for tr in root.cssselect('tr'):
+ mac, connected, auth, ssid, iface = [td.text_content().strip() for td in tr.getchildren()]
+ if mac == "MAC":
+ continue
+ connected = connected.lower() == 'yes'
+ byMac[mac] = dict(mac=mac, connected=connected, auth=auth == 'Yes', ssid=ssid, iface=iface)
+ resp = yield fetch('http://10.2.0.2/DHCPTable.asp', headers=headers)
+ for row in re.findall(r'new AAA\((.*)\)', resp.body):
+ clientHostname, ipaddr, mac, expires, iface = [s.strip("'") for s in row.rsplit(',', 4)]
+ if clientHostname == 'wlanadv.none':
+ continue
+ byMac.setdefault(mac, {}).update(dict(
+ clientHostname=clientHostname, connection=iface, ipaddr=ipaddr, dhcpExpires=expires))
+
+ returnValue(sorted(byMac.values()))
+
def jsValue(js, variableName):
# using literal_eval instead of json parser to handle the trailing commas