Mercurial > code > home > repos > homeauto
diff service/wifi/wifi.py @ 1226:7de8f0cd3392
very big rewrite. py3; orbi-only for now; n3 config file; delete or move out dead code
Ignore-this: bfecea6f4990d34b36cc6d97cc6c6fa2
darcs-hash:79a3d55bfae5776b2374faa2020b6e27f17eb390
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Sat, 30 Mar 2019 23:38:47 -0700 |
parents | b8c0daabe5a5 |
children | ec0db53aa833 |
line wrap: on
line diff
--- a/service/wifi/wifi.py Sat Mar 30 18:59:19 2019 -0700 +++ b/service/wifi/wifi.py Sat Mar 30 23:38:47 2019 -0700 @@ -1,4 +1,3 @@ -#!/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. @@ -11,25 +10,25 @@ Todo: this should be the one polling and writing to mongo, not entrancemusic """ -from __future__ import division -import sys, cyclone.web, json, traceback, time, pystache, datetime, logging +import sys, json, traceback, time, datetime, logging +from typing import List + from cyclone.httpclient import fetch - from dateutil import tz +from influxdb import InfluxDBClient +from pymongo import MongoClient as Connection, DESCENDING +from rdflib import Namespace, Literal, ConjunctiveGraph from twisted.internet import reactor, task from twisted.internet.defer import inlineCallbacks +import ago +import cyclone.web import docopt -from influxdb import InfluxDBClient -from pymongo import MongoClient as Connection, DESCENDING -from rdflib import Namespace, Literal, URIRef, ConjunctiveGraph - -from scrape import Wifi, macUri - -from patchablegraph import PatchableGraph, CycloneGraphEventsHandler, CycloneGraphHandler +import pystache from cycloneerr import PrettyErrorHandler from logsetup import log - +from patchablegraph import PatchableGraph, CycloneGraphEventsHandler, CycloneGraphHandler +from scrape import Wifi, SeenNode AST = Namespace("http://bigasterisk.com/") DEV = Namespace("http://projects.bigasterisk.com/device/") @@ -38,7 +37,6 @@ 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) @@ -61,8 +59,7 @@ return lastArrive['created'] def connectedAgoString(conn): - return web.utils.datestr( - conn.astimezone(tz.tzutc()).replace(tzinfo=None)) + return ago.human(conn.astimezone(tz.tzutc()).replace(tzinfo=None)) class Table(PrettyErrorHandler, cyclone.web.RequestHandler): def get(self): @@ -88,7 +85,6 @@ rows=sorted(map(rowDict, self.settings.poller.lastAddrs), key=lambda a: (not a.get('connected'), a.get('name')))))) - class Json(PrettyErrorHandler, cyclone.web.RequestHandler): def get(self): @@ -97,13 +93,13 @@ if age > 10: raise ValueError("poll data is stale. age=%s" % age) self.write(json.dumps({"wifi" : self.settings.poller.lastAddrs, - "dataAge" : age})) + "dataAge" : age})) class Poller(object): def __init__(self, wifi, mongo): self.wifi = wifi self.mongo = mongo - self.lastAddrs = [] + self.lastAddrs = [] # List[SeenNode] self.lastWithSignal = [] self.lastPollTime = 0 @@ -113,50 +109,42 @@ @inlineCallbacks def poll(self): - now = int(time.time()) - - # UVA mode: - addDhcpData = lambda *args: None - try: newAddrs = yield self.wifi.getPresentMacAddrs() - addDhcpData(newAddrs) - - newWithSignal = [a for a in newAddrs if a.get('connected')] - - actions = self.computeActions(newWithSignal) - points = [] - for action in actions: - log.info("action: %s", action) - action['created'] = datetime.datetime.now(tz.gettz('UTC')) - mongo.save(action) - points.append( - self.influxPoint(now, action['address'].lower(), - 1 if action['action'] == 'arrive' else 0)) - try: - self.doEntranceMusic(action) - except Exception as e: - log.error("entrancemusic error: %r", e) - - if now // 3600 > self.lastPollTime // 3600: - log.info('hourly writes') - for addr in newWithSignal: - points.append(self.influxPoint(now, addr['mac'].lower(), 1)) - - influx.write_points(points, time_precision='s') - self.lastWithSignal = newWithSignal - if actions: # this doesn't currently include signal strength changes - fetch(reasoning + "immediateUpdate", - method='PUT', - timeout=2, - headers={'user-agent': ['tomatoWifi']}).addErrback(log.warn) - self.lastAddrs = newAddrs - self.lastPollTime = now - - self.updateGraph(masterGraph) + self.onNodes(newAddrs) except Exception as e: log.error("poll error: %r\n%s", e, traceback.format_exc()) + def onNodes(self, newAddrs: List[SeenNode]): + now = int(time.time()) + newWithSignal = [a for a in newAddrs if a.connected] + + actions = self.computeActions(newWithSignal) + points = [] + for action in actions: + log.info("action: %s", action) + action['created'] = datetime.datetime.now(tz.gettz('UTC')) + mongo.save(action) + points.append( + self.influxPoint(now, action['address'].lower(), + 1 if action['action'] == 'arrive' else 0)) + if now // 3600 > self.lastPollTime // 3600: + log.info('hourly writes') + for addr in newWithSignal: + points.append(self.influxPoint(now, addr.mac.lower(), 1)) + + influx.write_points(points, time_precision='s') + self.lastWithSignal = newWithSignal + if actions: # this doesn't currently include signal strength changes + fetch(reasoning + "immediateUpdate", + method='PUT', + timeout=2, + headers={'user-agent': ['wifi']}).addErrback(log.warn) + self.lastAddrs = newAddrs + self.lastPollTime = now + + self.updateGraph(masterGraph) + def influxPoint(self, now, address, value): return { 'measurement': 'presence', @@ -168,46 +156,27 @@ def computeActions(self, newWithSignal): actions = [] - def makeAction(addr, act): + def makeAction(addr: SeenNode, act: str): d = dict(sensor="wifi", - address=addr.get('mac').upper(), # mongo data is legacy uppercase - name=addr.get('name'), - networkName=addr.get('clientHostname'), + address=addr.mac.upper(), # mongo data is legacy uppercase action=act) - if act == 'arrive' and 'ip' in addr: + if act == 'arrive': # 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'] + d['ip'] = addr.ip return d for addr in newWithSignal: - if addr['mac'] not in [r['mac'] for r in self.lastWithSignal]: + if addr.mac not in [r.mac for r in self.lastWithSignal]: actions.append(makeAction(addr, 'arrive')) for addr in self.lastWithSignal: - if addr['mac'] not in [r['mac'] for r in newWithSignal]: + if addr.mac not in [r.mac for r in newWithSignal]: actions.append(makeAction(addr, 'leave')) return actions - - # these need to move out to their own service - def doEntranceMusic(self, action): - import restkit, json - dt = self.deltaSinceLastArrive(action['name']) - log.debug("dt=%s", dt) - if dt > datetime.timedelta(hours=1): - hub = restkit.Resource( - # PSHB not working yet; "http://bang:9030/" - "http://slash:9049/" - ) - action = action.copy() - del action['created'] - del action['_id'] - log.info("post to %s", hub) - hub.post("visitorNet", payload=json.dumps(action)) - def deltaSinceLastArrive(self, name): results = list(self.mongo.find({'name' : name}).sort('created', DESCENDING).limit(1)) @@ -218,7 +187,6 @@ return now - last def updateGraph(self, masterGraph): - g = ConjunctiveGraph() ctx = DEV['wifi'] @@ -230,43 +198,30 @@ raise ValueError("poll data is stale. age=%s" % age) for dev in self.lastAddrs: - if not dev.get('connected'): + if not dev.connected: continue - mac = dev['mac'].lower() - uri = macUri(mac) - g.add((uri, ROOM['macAddress'], Literal(mac), ctx)) + g.add((dev.uri, ROOM['macAddress'], Literal(dev.mac), ctx)) + g.add((dev.uri, ROOM['ipAddress'], Literal(dev.ip), ctx)) - g.add((uri, ROOM['connected'], { - 'wireless': AST['wifiAccessPoints'], - '2.4G': AST['wifiAccessPoints'], - '5G': AST['wifiAccessPoints'], - '-': AST['wifiUnknownConnectionType'], - 'Unknown': AST['wifiUnknownConnectionType'], - 'wired': AST['houseOpenNet']}[dev['contype']], ctx)) - if 'clientHostname' in dev and dev['clientHostname']: - g.add((uri, ROOM['wifiNetworkName'], Literal(dev['clientHostname']), ctx)) - if 'name' in dev and dev['name']: - g.add((uri, ROOM['deviceName'], Literal(dev['name']), ctx)) - if 'signal' in dev: - g.add((uri, ROOM['signalStrength'], Literal(dev['signal']), ctx)) - if 'model' in dev: - g.add((uri, ROOM['networkModel'], Literal(dev['model']), ctx)) + for s,p,o in dev.stmts: + g.add((s, p, o, ctx)) + try: - conn = whenConnected(mongo, dev['mac']) + conn = whenConnected(mongo, dev.mac) except ValueError: traceback.print_exc() pass else: - g.add((uri, ROOM['connectedAgo'], + g.add((dev.uri, ROOM['connectedAgo'], Literal(connectedAgoString(conn)), ctx)) - g.add((uri, ROOM['connected'], Literal(conn), ctx)) + g.add((dev.uri, ROOM['connected'], Literal(conn), ctx)) masterGraph.setToGraph(g) if __name__ == '__main__': args = docopt.docopt(''' Usage: - tomatoWifi [options] + wifi.py [options] Options: -v, --verbose more logging @@ -282,8 +237,11 @@ mongo = Connection('bang', 27017, tz_aware=True)['visitor']['visitor'] influx = InfluxDBClient('bang', 9060, 'root', 'root', 'main') + config = ConjunctiveGraph() + config.parse(open('private_config.n3'), format='n3') + masterGraph = PatchableGraph() - wifi = Wifi() + wifi = Wifi(config) poller = Poller(wifi, mongo) task.LoopingCall(poller.poll).start(1/float(args['--poll'])) @@ -301,5 +259,4 @@ wifi=wifi, poller=poller, mongo=mongo)) - import twisted; print('twisted', twisted.__version__) reactor.run()