# -*- coding: utf-8 -*- #---Standard library imports import string import sys import logging import time from datetime import datetime from collections import namedtuple, defaultdict, OrderedDict import json import traceback from functools import wraps import Queue import threading import hashlib from UserDict import UserDict import random #---Third-party imports from utils.path import path if sys.platform == 'win32': import win32api import win32con from configobj import ConfigObj, flatten_errors import validate import cherrypy from cherrypy.lib.static import serve_file from cherrypy.lib.http import HTTPDate from dateutil import parser import scipy from runphoton import BBox, Resolution from utils.materials import materialsAsXml, Brdf, loadMaterialsFromString from utils import daemonthread, loadLocations from utils.xml import StringToTree, TreeToString, subelement import evaluate import render import optimize as optimizer import simulate as simulator import plotter import localize #---Globals CONFIG_SPEC = path(__file__).parent.joinpath('config/configspec.ini') PATH_STATIC = path(__file__).parent.joinpath('static').abspath() LOCALIZATION_ON = True LOCALIZATION_ON = False LOGFILE = path('.').joinpath('out.log') MEASUREMENTS_STORAGE = path('.').joinpath('measurements.txt') if not MEASUREMENTS_STORAGE.exists(): MEASUREMENTS_STORAGE.touch() # first substitution is station name, second is apid OPTIMIZE_FILES = '/home/dirk/loco/maps/UMIC/experiment/measure_%s_%s.txt' log = logging.getLogger('lws') app = None def _splitBaseurl(baseurl): if baseurl is None: return None, None parts = baseurl.split(':') if len(parts) == 1: parts.append(None) return parts[0], parts[1] def getHTTPConfig(): c = urlparse.urlsplit(cherrypy.request.base) host, port = _splitBaseurl(c[1]) if app.config['behindProxy']: port = app.config['proxyHttpPort'] host = _splitBaseurl(urlparse.urlsplit(app.config['proxyBase'])[1])[0] else: port = app.config['httpPort'] return host, port def setupLogging(): log.handlers = [] log.setLevel(logging.DEBUG) sh = logging.StreamHandler() formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s", "%m-%d %H:%M:%S") sh.setFormatter(formatter) log.addHandler(sh) logfile = LOGFILE log.info('storing logfile to %s' % logfile.normpath()) fh = logging.FileHandler(logfile, 'a') fh.setFormatter(logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s", '%m-%d %X')) log.addHandler(fh) logging.getLogger('cherrypy.error').addHandler(fh) def setForceNoCache(expires_past=HTTPDate(1169942400.0)): headers = cherrypy.response.headers headers['Expires'] = expires_past headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' headers['Pragma'] = 'no-cache' def nocache(func): @wraps(func) def wrapper(*args, **kwargs): setForceNoCache() return func(*args, **kwargs) return wrapper Measurement = namedtuple('Measurement', 'x y z avgrssi rssis') APInfo = namedtuple('AP', 'rssi remotets localts delta') class MobileStation(object): def __init__(self, name): self.name = name self.data = defaultdict(list) # key: (bssid, ssid), values: list of apinfo class LWS(object): def __init__(self, configfile, cfgoverlay=None): self.mstations = {} # key: station name, value: MobileStation() self.mac2cfg = {} # key: mac address, value: config section for corresponding access point id self.aliasedmac2apid = {} # key: mac, value corresponding apid self.activeScene = None # points to sctive scene section in config self.configfile = path(configfile) self.cfgoverlay = cfgoverlay # nested dict to overwrite config values self._loadConfig() self.measure_enabled = None # beocmes tuple (station name, locid, startts, duration) self.measurement_stopped = None self.pathtrack_enabled = None self.lastCollectedPath = None # becomes a dict with key: station name and value a dict with key: locid and value: {(bssid, ssid): APInfo()} self.tracked_measurements = defaultdict(dict) # dict with key stationname, and value: dict with key pathid, and value: list (locid, timestamp) self.tracked_positions = defaultdict(dict) # in memory storage of measurements (from data in MEASUREMENTS_STORAGE) - like self.tracked_measurements self.stored_measurements = defaultdict(lambda: defaultdict(list)) self.loadMeasurementDump() if not self.activeScene['locationfile'].exists(): print 'creating locfile at %s' % self.activeScene['locationfile'].abspath() self.activeScene['locationfile'].touch() self.known_locations = loadLocations(self.activeScene['locationfile']) # key: locid, value: measure.Location() daemonthread(target=self._checkConfig, name="configReloadLoop") self.simulator = simulator.Simulator(self.config['tmp']) self.hash2objfile = {} self.loadobjfiles() optimizer.app = self self.optimizer = optimizer.Optimizer() self.plotLock = threading.Lock() self.writeLock = threading.Lock() # measurements for different APs self._apid2measurements = {} self.localization_data = defaultdict(lambda: Queue.Queue()) self.last_result = defaultdict(lambda: Queue.Queue()) self.device2localizationenv = {} self.localizeSessions = defaultdict(set) self.localizeSessionInactiveAPs = defaultdict(set) # key: device, value: set of deactivated APs self.draw_aptiles_lock = threading.Lock() def loadobjfiles(self): objpath = self.config['tmp'].joinpath('obj') if not objpath.exists(): objpath.mkdir() for f in objpath.files(): h = hashlib.sha1(f.bytes()).hexdigest() self.hash2objfile[h] = f def _loadConfig(self): # check file exists and is readable f = open(self.configfile) f.read(1024) f.close() log.info('loading configfile %s' % self.configfile) configSpec = ConfigObj(CONFIG_SPEC, encoding='utf-8', list_values=False) validator = validate.Validator() self.config = ConfigObj(self.configfile, configspec=configSpec, interpolation='template', encoding='UTF-8') res = self.config.validate(validator, preserve_errors=True) #check for errors for entry in flatten_errors(self.config, res): # each entry is a tuple section_list, key, error = entry section_list.append('[missing section]') if key is None else section_list.append(key) section_string = ', '.join(section_list) if not error: error = 'Missing value or section.' raise Exception, '%s=%s' % (section_string, error) def _applyOverlay(section, d): for key, value in d.items(): if isinstance(d[key], dict): _applyOverlay(section[key], d[key]) else: section[key] = value if self.cfgoverlay: _applyOverlay(self.config, self.cfgoverlay) for k in 'tmp', 'tmp_apdata', 'tmp_optruns', 'tmp_tracked': self.config[k] = path(self.config[k]).abspath() if not self.config[k].exists(): self.config[k].mkdir() # check objfile fs paths for secname in self.config['scenes'].sections: for fname in ('objfile', 'locationfile', 'measurementsfile'): if secname == 'onewall': # special default lookup under ./HERE/static f = path(__file__).parent.joinpath('setups', secname, self.config['scenes'][secname][fname]) else: f = path(self.config['scenes'][secname][fname]) self.config['scenes'][secname][fname] = f if not fname in ('locationfile', 'measurementsfile') and not f.exists(): log.warn('configured %s %s for scene %s does not exist' % (fname, f.abspath(), secname)) sys.exit() active = self.config['scenes']['active'] self.activeScene = self.config['scenes'][active] if active == 'onewall' and len(self.config['aps'].sections) == 0: # add a default AP for onewall scene class FAKE(UserDict): pass for i, (name, x, y, z) in enumerate([('ap1', -6.0, -1.0, -2.0), ('ap2', 0.0, 0.0, 0.0)]): faked_ap = FAKE() self.config['aps'][name] = faked_ap faked_ap.name = name faked_ap['mac'] = '00:00:00:00:00:0' + str(i) faked_ap['powerid'] = 'default' faked_ap['power'] = 0.1 faked_ap['x'] = x faked_ap['y'] = y faked_ap['z'] = z faked_ap['on'] = True faked_ap['optimize'] = True self.config['aps'].sections.append(name) self.aliasedmac2apid.clear() apcfgs = self.config['aps'] for apid in apcfgs.sections: assert apcfgs[apid]['mac'] is not None, 'invalid cfg for %s: %r' % (apid, apcfgs[apid]) self.mac2cfg[apcfgs[apid]['mac']] = apcfgs[apid] for mac in apcfgs[apid]['aliases']: self.aliasedmac2apid[mac] = apid #~ if apid != '107': #~ self.config['aps'][apid]['optimize'] = False # add reverse paths for pathid in list(self.activeScene['paths'].scalars): if not pathid.startswith('still_'): self.activeScene['paths']['%s_r' % pathid] = list(reversed(self.activeScene['paths'][pathid])) def _checkConfig(self): last_mtime = self.configfile.mtime while True: try: if self.configfile.mtime > last_mtime: self._loadConfig() last_mtime = self.configfile.mtime except Exception: log.exception('error during loading config from %s' % self.configfile.normpath()) time.sleep(2) @cherrypy.expose def index(self, **kwargs): reload(render) return render.renderIndex(self) @cherrypy.expose def localize(self, name, **kwargs): reload(render) return render.renderLocalize(self, name, refresh='refresh' in kwargs) @cherrypy.expose def view(self, name): if not name in self.mstations: return ''' unknown station: "%s"''' % name reload(render) return render.renderView(self, self.mstations[name]) @cherrypy.expose def measure(self, name): ''' collect measurements for training raytracer params ''' reload(render) return render.renderMeasure(self, self.mstations.get(name) ) @cherrypy.expose def locate(self, name): if not name in self.mstations: return ''' unknown station: "%s"''' % name reload(render) return render.renderLocate(self, self.mstations[name]) @cherrypy.expose def cluster(self): reload(render) return render.renderCluster(self) @cherrypy.expose def measurements(self, view=None, stations=None): reload(render) if stations is None: stations = self.config['knownStations'] else: stations = [e.strip() for e in stations.split(',') if e.strip()] if view is not None: view = view.split(',') else: view = ['mean', 'median', 'stddev'] return render.renderMeasurements(self, stations=stations, view=view) @cherrypy.expose def optimize(self): reload(render) return render.renderOptimizer(self) @cherrypy.expose def optruns(self, optruns='', **kwargs): reload(render) return render.renderEvaluteOptruns(self, optruns, compact='compact' in kwargs, **kwargs) @cherrypy.expose def plotHistogram(self, **kwargs): plotfile = self.config['tmp'].joinpath('_hist.png') reload(plotter) with self.plotLock: plotter.plotHistogram(plotfile, **kwargs) return serve_file(plotfile) @cherrypy.expose def collect(self, station): ''' collect a path for using in locatization''' reload(render) return render.renderCollectPath(self, station) @cherrypy.expose def evaluate(self, station, optrun=None, cubewidth=3, errtype='2d', filter=None, algo='hmm', **kwargs): ''' evalute collected measurements''' reload(render) return render.renderEvaluate(self, optrun, station, int(cubewidth), errtype=errtype, refresh='refresh' in kwargs and not kwargs['refresh'] == False, filter=filter, algo=algo) @cherrypy.expose def evaluatePath(self, station, optrun, pathid, runid, setting, cubewidth=3, algo='hmm', **kwargs): ''' evalute collected measurements''' reload(render) if pathid == 'LASTCOLLECTED': pathid = self.lastCollectedPath[1] if runid == 'LASTCOLLECTED': runid = self.lastCollectedPath[2] return render.renderEvaluatePath(self, optrun, station, pathid, runid, setting, int(cubewidth), algo, refresh='refresh' in kwargs and not kwargs['refresh'] == False) @cherrypy.expose def startOptimizer(self, optsession, station, power, density, numphotons, resolution, bbox, scene, startpop, childcount, childcull, mutprob=0.1, mutamt=0.1, resume='', neworgs=0, timelimit=0, genlimit=0, **kwds): # forbid these two chars in name as they have some internal semantics optsession = optsession.replace('_', '.').replace(',', '.') power = float(power) density = float(density) numphotons = int(numphotons) bbox = [float(e) for e in bbox.split(',') if e.strip()] bbox = BBox(x1=bbox[0], x2=bbox[1], y1=bbox[2], y2=bbox[3], z1=bbox[4], z2=bbox[5]) if ',' in resolution: # its a triple res = [int(e) for e in resolution.split(',')] resolution = Resolution(res[0], res[1], res[2]) else: # its only the x value - calculate the rest to ensure the other two dims have the same pixel/m ratio xres = int(resolution) xr, yr, zr = bbox.x2 - bbox.x1, bbox.y2 - bbox.y1, bbox.z2 - bbox.z1 resolution = Resolution(x=xres, y=int(float(xres) * yr / xr), z=int(float(xres) * zr / xr)) startpop = int(startpop) childcount = int(childcount) childcull = int(childcull) self.optimizer.startOptimizeRun(optsession, station, power, density, numphotons, resolution, bbox, scene, startpop, childcount, childcull, float(mutprob), float(mutamt), resume=resume.strip(), neworgs=int(neworgs), timelimit=timelimit, genlimit=genlimit) raise cherrypy.HTTPRedirect(cherrypy.request.headers['Referer']) @cherrypy.expose def stopOptimizer(self): self.optimizer.stopOptimizeRun() raise cherrypy.HTTPRedirect(cherrypy.request.headers['Referer']) @cherrypy.expose def getAPInfo(self, name, jsid, filter=None): cherrypy.response.headers['Content-Type'] = 'application/json' if filter is not None: filter = {e for e in filter.split(',') if e.strip()} if not name in self.mstations: return json.dumps({}) if not 'lastdelivered' in cherrypy.session: cherrypy.session['lastdelivered'] = {} #key: jsid, value: last delivered ts lastdelivered = cherrypy.session['lastdelivered'] ld = lastdelivered.get(jsid) lastdelivered[jsid] = datetime.now() resdata = {} for (mac, apname), infolist in self.mstations[name].data.items(): if filter is not None: if not mac in self.mac2cfg: continue apid = self.mac2cfg[mac].name if not apid in filter: continue if ld is not None: _infolist = [apinfo for apinfo in infolist if apinfo.localts > ld] else: _infolist = infolist[-5:] resdata[mac.replace(':', '_')] = _infolist dthandler = lambda obj: obj.strftime('%H:%M:%S') if isinstance(obj, datetime) else None return json.dumps(resdata, default=dthandler) @cherrypy.expose def startMeasureLoc(self, name, locid, duration): cherrypy.response.headers['Content-Type'] = 'application/json' if not locid.isdigit() or not int(locid) in self.known_locations: valid = ','.join(str(e) for e in self.known_locations) return json.dumps({'started': '', 'error': 'invalid location %s [valid: %s]' % (locid, valid)}) duration = float(duration) startts = time.time() with self.writeLock, open(MEASUREMENTS_STORAGE, 'a') as f: f.write('start: %s %s %s\n' % (name, locid, datetime.fromtimestamp(startts))) self.measure_enabled = (name, locid, startts, duration) # setup list self.tracked_measurements[name][locid] = defaultdict(list) def waitForEnd(): while time.time() < startts + duration: time.sleep(0.1) self.measure_enabled = None self.measurement_stopped = datetime.now() with self.writeLock, open(MEASUREMENTS_STORAGE, 'a') as f: aps = self.tracked_measurements[name][locid].items() log.info('storing measurements for %s access points' % len(aps)) run = {} for (bssid, ssid), data in aps: if any(not c in string.printable for c in ssid): log.warn('got corrupt ssid: %s' % repr(ssid)) continue apid = '???' if not bssid in self.mac2cfg else self.mac2cfg[bssid].name s = '%s %s %s %s %s rssi: ' % (name, locid, apid, bssid, ssid) f.write(s) f.write(','.join(str(apinfo.rssi) for apinfo in data) + '\n') run[bssid] = [apinfo.rssi for apinfo in data] f.write('end: %s %s %s\n' % (name, locid, self.measurement_stopped)) self.stored_measurements[name][int(locid)].append((self.measurement_stopped, run)) daemonthread(target=waitForEnd, name="waitForEnd_%s" % name) return json.dumps({'started': str(datetime.now()).split()[1]}) def getMeasurements(self, station, apid, fromMeasurementStore=True): if fromMeasurementStore: if not self.activeScene['measurementsfile']: return None key = (station, apid) measurements, ts = self._apid2measurements.get(key, (None, None)) f = path(self.activeScene['measurementsfile'] % (station, apid)) if measurements is None or (f.exists() and f.mtime > ts): measurements = {} try: if not f.exists(): log.warn('no measurement data found at %s' % f.abspath()) self._apid2measurements[key] = (None, None) return None else: log.info('loading measurement data from %s' % f.abspath()) for line in f.lines(): if not line.strip(): continue first, all_rssis = line.split(':') locid, x, y, z, avgrssi = first.split() rssis = [float(e) for e in all_rssis.split(',')] measurements[locid] = Measurement(float(x), float(y), float(z), float(avgrssi), rssis) self._apid2measurements[key] = (measurements, f.mtime) except Exception: log.error('cannot load measurements from %s' % f.abspath()) measurements = {} return measurements else: measurements = {} MIN_DIST = 0.2 # only use measurements that are near a location # from tracked path pathids2runids = evaluate.getCollectedPathids2runids(station, self.activeScene['paths'], self.config['tmp_tracked']) for pathid, runs in pathids2runids.items(): log.debug('extracting measurements from %s/%s/%s' % (apid, pathid, ','.join(runs))) locid2rssis = defaultdict(list) locids = [int(e) for e in self.activeScene['paths'][pathid]] for runid in runs: try: ms = localize.Measurements.fromTrackedMeasurements(self.known_locations, self.config['tmp_tracked'], station, pathid, runid) except Exception: log.error('cannot read path %s/%s' % (pathid, runid)) continue nearestMeasurements = defaultdict(lambda : (None, None)) for m in ms: for locid in locids: loc = self.known_locations[locid] _m, distance = nearestMeasurements[locid] curr_dist = ((m.pos.x - loc.x)**2 + (m.pos.y - loc.y)**2 + + (m.pos.z - loc.z)**2)**0.5 if distance is None or curr_dist < distance: nearestMeasurements[locid] = (m, curr_dist) for locid in locids: m, distance = nearestMeasurements[locid] rssi = m.apdata.get(apid) if distance < MIN_DIST and rssi is not None: locid2rssis[locid].append(rssi) # now proces all collected rssis for the given path for locid, rssis in locid2rssis.items(): loc = self.known_locations[locid] if len(rssis) > 0: avgrssi = sum(rssis) / len(rssis) measurements[str(locid)] = Measurement(float(loc.x), float(loc.y), float(loc.z), float(avgrssi), rssis) return measurements def loadMeasurementDump(self): ''' load measurements from file that includes all stations ''' self.stored_measurements.clear() run = {} for i, l in enumerate(MEASUREMENTS_STORAGE.lines()): try: if l.startswith('start:'): l2 = l.split() _, station, locid, d1, d2 = l2 run[(station, locid)] = {} elif l.startswith('end:'): l2 = l.split() if len(l2) == 5: _, station, locid, d1, d2 = l2 if (station, locid) in run: dt = parser.parse('%s %s' % (d1, d2)) self.stored_measurements[station][int(locid)].append((dt, run[(station, locid)])) #~ self.stored_measurements[self.active_station] run.pop((station, locid), None) else: if 'rssi:' in l: meta, rssis = l.split('rssi:') meta = meta.split() station, locid, apid, mac = meta[:4] rssis = [float(e) for e in rssis.split(',') if e.strip()] if (station, locid) in run: run[(station, locid)][apid] = rssis except Exception: log.exception('error during parsing line %s: %s]' % (i+1, l)) @cherrypy.expose def getMeasurementResults(self, name, locid): cherrypy.response.headers['Content-Type'] = 'application/json' apdata = {} if self.measure_enabled is None: aps = self.tracked_measurements[name][locid].items() for (bssid, ssid), data in aps: rssis = [apinfo.rssi for apinfo in data] avg_rssi = sum(rssis) / float(len(rssis)) if len(rssis) > 0 else 0 apdata[bssid.replace(':', '_')] = {'rssis': rssis, 'avg': '%.1f' % avg_rssi} # signal still running measurement with endts = "" endts = str(self.measurement_stopped).split()[1] if self.measurement_stopped is not None else '' return json.dumps({'data': apdata, 'stopped': endts, }) @nocache @cherrypy.expose def buildOptimizefile(self, station): cherrypy.response.headers['Content-Type'] = 'text/plain' self.loadMeasurementDump() log.info('known locids: %s' % ','.join(str(e) for e in self.known_locations.keys())) apid2locs = defaultdict(lambda: defaultdict(list)) for locid in self.stored_measurements[station]: for dt, runs in self.stored_measurements[station][locid]: for apid in runs: # ignore all mac addresses, that are not configured if not apid in self.config['aps']: continue apid2locs[apid][locid].extend(runs[apid]) res = '' for apid, locs in apid2locs.items(): outfile = OPTIMIZE_FILES % (station, apid) res += 'write to %s\n' % path(outfile).abspath() with open(outfile, 'w') as f: for locid, rssis in locs.items(): if not locid in self.known_locations: log.warn('skipping unknown location %s' % locid) continue loc = self.known_locations[locid] avg_sig = sum(rssis) / float(len(rssis)) _str_rssis = ','.join(str(e) for e in rssis) f.write('%s %s %s %s %.2f : %s\n' % (locid, loc.x, loc.y, loc.z, avg_sig, _str_rssis)) res += '%s\n' % path(outfile).text() return res @cherrypy.expose def initDevice(self, name): cherrypy.response.headers['Content-Type'] = 'text/plain' return 'true 0.5' def _localize(self, device): SHUTDOWN_TIMEOUT = 10 #seconds CUBE_WIDTH = 3 COUNT = 2000 try: # init stuff env = self.getLocalizationEnv(device) last_ts = time.time() lmse = localize.LMSELocalizer(env, cubewidth=CUBE_WIDTH, verbose=False, with_history=True) hmm = localize.HMMLocalizer(env, cubewidth=CUBE_WIDTH, verbose=False) #~ localizers = [('lmse', lmse), ('hmm', hmm)] localizers = [('hmm', hmm)] #~ localizers = [('lmse', lmse)] #~ localizers = [] while True: try: # get from Queue and raises Queue.empty exception data = self.localization_data[device].get(timeout=0.03) ts2apdata = defaultdict(dict) for info in data: cfg = self.mac2cfg.get(info['bssid']) if not cfg: continue apid = cfg.name if apid in self.localizeSessionInactiveAPs[device]: continue ts = round(info['ts'], 1) # round to 100ms granularity if 'pos' in info: pos = tuple(info['pos']) else: pos = (0,0,0) ts2apdata[(pos, ts)][apid] = info['rssi'] measurements = [] for (pos, ts), apdata in sorted(ts2apdata.items()): remotets = datetime.fromtimestamp(ts) measurements.append(localize.Measurement(remotets, apdata, pos)) tt = time.time() for curr_m in measurements: for algo, localizer in localizers: paths, measurements_modified = localizer.evaluateNext(localize.Measurements([curr_m])) measurements_a = measurements_modified.asArray(localizer.apids) #~ xyz = paths['seq'][0] for mm, ma, i in zip(measurements_modified, measurements_a, reversed(range(1, len(paths.values()[0])+1))): xyzs = [(env.cube2meter(x, y ,z , cubewidth=CUBE_WIDTH), costs) for x, y, z, costs in localizer.decoder.history[-i][-COUNT:]] res = [(x, y, z, costs, ma, mm) for (x, y, z), costs in xyzs] for sessionid in list(self.localizeSessions[device]): self.last_result[(device, sessionid, algo)].put(res) #~ log.debug('both in %.3f secs' % (time.time() - tt) except Queue.Empty: if time.time() - last_ts > SHUTDOWN_TIMEOUT: log.info('shutdown localizer worker') break time.sleep(0.02) continue else: last_ts = time.time() finally: self.localization_data.pop(device) self.localizeSessions[device].clear() self.localizeSessionInactiveAPs.clear() def getLocalizationEnv(self, device): if not device in self.device2localizationenv: locfile = self.activeScene['locationfile'] objfile = self.activeScene['objfile'] aps = [] for apid in self.config['aps'].sections: apcfg = self.config['aps'][apid] aps.append((apid, apcfg['x'], apcfg['y'], apcfg['z'])) # HARDCODE #optrun = 'iconia.basic5mat_1' #optrun = 'optsession_1_swaped' #optrun = 'optnetwork4_1' optrun = self.config['serve_localize_apdata'] if not optrun: print "Error: No optrun" else: print "optrun: ", optrun optrundir = self.config['tmp_apdata'].joinpath(optrun) vip = optrundir.joinpath(self.activeScene.name + '_%s.dat') env = localize.Environment(objfile=objfile, aps=aps, locationfile=locfile, tmpdir=self.config['tmp'], vi_path=vip, bbox2d=self.activeScene['bbox'][:4], davalues=[]) self.device2localizationenv[device] = env return self.device2localizationenv[device] @cherrypy.expose def newData(self, name, data): ''' receive new data from a measuring device ''' try: localts = datetime.now() if isinstance(data, basestring): #TODO: use json? data = eval(data) if not name in self.mstations: self.mstations[name] = MobileStation(name) unique_aps = len({e['bssid'] for e in data}) unique_ts = len({e['ts'] for e in data}) log.info('receiving data from %s with %s aps [%s unique ts]' % (name, unique_aps, unique_ts)) station = self.mstations[name] # this is probably not threadsafe if not name in self.localization_data: queue = self.localization_data[name] daemonthread(target=self._localize, name="localize_%s" % name, args=(name,)) else: queue = self.localization_data[name] queue.put(data) ## check aliases rssids_from_aliased = defaultdict(list) for info in data: mac = info['bssid'] apid = self.aliasedmac2apid.get(mac) if apid is not None: found = False # first check if alias target is in data for info2 in data: if self.mac2cfg.get(info2['bssid']) == apid: found = True if not found: # change the mac of this info record info['bssid'] = self.config['aps'][apid]['mac'] else: rssids_from_aliased[apid].append(info['rssi']) last_rssi = {} for info in data: # (mac, ssid) key = (info['bssid'], info['ssid']) if not key in last_rssi and key in station.data: last_rssi[key] = station.data[key][-1].rssi if not key in last_rssi: delta = 0 else: delta = last_rssi[key] - info['rssi'] rssi = info['rssi'] if info['bssid'] in self.mac2cfg and self.mac2cfg[info['bssid']].name in rssids_from_aliased: rssis = rssids_from_aliased[self.mac2cfg[info['bssid']].name] rssi = sum(rssis + [rssi]) / (len(rssis) + 1) last_rssi[key] = rssi api = APInfo(rssi=rssi, remotets=datetime.fromtimestamp(info['ts']), localts=localts, delta=delta) #~ print api.localts - api.remotets if self.measure_enabled is not None: stationname, locid, _, _ = self.measure_enabled measurement_info = self.tracked_measurements[stationname][locid] measurement_info[key].append(api) elif self.pathtrack_enabled is not None: stationname, pathid, _ = self.pathtrack_enabled measurement_info = self.tracked_measurements[stationname][pathid] measurement_info[key].append(api) station.data[key].append(api) except Exception: log.exception('error during receiving data') @cherrypy.expose def uploadobjfile(self, expectedhash, objfile): ''' remote guis can upload their locally modified obj files over this interface''' objpath = self.config['tmp'].joinpath('obj') s = objfile.file.read() h = hashlib.sha1(s).hexdigest() assert h == expectedhash name = path(objfile.filename.replace('\\', '/').split('/')[-1]).namebase f = objpath.joinpath('%s_%s.obj' % (h, name)) log.info('storing objfile [%.1f kb] to %s...' % (len(s) / 1024.0, f.abspath())) f.write_bytes(s) self.hash2objfile[h] = f @cherrypy.expose def queueJob(self, scenename, objhash, materials, ap, bbox, resolution, density, numphotons, disabledObjects=''): ''' queue a rendering job from a remote gui''' cherrypy.response.headers['Content-Type'] = 'text/xml' if not objhash in self.hash2objfile: log.warn('need new obj file') return 'unknown objfile' objfile = self.hash2objfile[objhash] # eval is quite unsecure - see http://stackoverflow.com/questions/661084/security-of-pythons-eval-on-untrusted-strings resolution = Resolution(**eval(resolution, {'__builtins__':[]}, {})) bbox = BBox(**eval(bbox, {'__builtins__':[]}, {})) ap = simulator.AccessPoint(**eval(ap, {'__builtins__':[]}, {})) materials = loadMaterialsFromString(materials) density = float(density) numphotons = int(numphotons) disabledObjects = disabledObjects.split(',') run = simulator.Run(scenename, objfile, materials, ap, bbox, resolution, density, numphotons, code='transfer_result', disabledObjects=disabledObjects) self.simulator.queue(run, simulator.HIGH_PRIO) log.info('queued run %s' % run.id) return '' % run.id @cherrypy.expose def getJob(self, worker): cherrypy.response.headers['Content-Type'] = 'text/xml' ctrlfile = self.simulator.getWork(worker) if ctrlfile is None: return '' return ctrlfile.text() @cherrypy.expose def transferFullResult(self, worker, runid, zippedfile): ''' called by worker_code_transfer_result - sends back .raw and .dat file''' zippeddata = zippedfile.file.read() self.simulator.storeTransferedResult(worker, runid, zippeddata) @cherrypy.expose def transferResultAtLocIDs(self, worker, runid, data): ''' called by worker_code_transfer_result - sends back .raw and .dat file data is a dict with key: locid, value: rssi ''' data = eval(data, {'__builtins__':[]}, {}) #~ t = time.time() self.simulator.storeResultAtLocIDs(worker, runid, data) #~ print time.time() - t @cherrypy.expose def queryJobs(self, runids): cherrypy.response.headers['Content-Type'] = 'text/xml' waiting, running, finished = self.simulator.queryRuns(runids.split(',')) tree = StringToTree('') tree.getroot().attrib['waiting'] = ','.join(waiting) tree.getroot().attrib['running'] = ','.join(running) tree.getroot().attrib['finished'] = ','.join(finished) return TreeToString(tree) @cherrypy.expose def getJobZipData(self, runid): for run in self.simulator.finished_jobs: if runid == run.id: for f in run.resultfiles: if f.endswith('.zip'): return f.bytes() @cherrypy.expose def getObjData(self, worker, objfile, runid): cherrypy.response.headers['Content-Type'] = 'text/plain' #~ run = simulator.Run.get(runid) log.info('serving objfile %s from run %s to worker %s' % (objfile, runid, worker)) of = self.simulator.tmpdir.joinpath(runid, objfile) return of.text() @cherrypy.expose def brokenJob(self, worker, runid): log.warn('signaling broken job [%s]' % cherrypy.request.headers.get('user-agent', '')) self.simulator.brokenRun(worker, runid) if 'Referer' in cherrypy.request.headers: # redirect to referer raise cherrypy.HTTPRedirect(cherrypy.request.headers['Referer']) @cherrypy.expose def optimizePlot(self, session, onlymins='false'): reload(plotter) sdir = self.optimizer.tmpdir.joinpath(session) if onlymins == 'true': lines = sdir.joinpath('mins.txt').lines()[:-1] plotfile = sdir.joinpath('_plot_mins.png') highlightmins = False else: lines = [] for f in list(sorted(sdir.files('population_*.txt'), key=lambda x: x.mtime)): lines.extend(f.lines()[:-1]) plotfile = sdir.joinpath('_plot_all.png') highlightmins = True max_mt = max([f.mtime for f in sdir.files('*.txt')] + [0]) if not plotfile.exists() or max_mt > plotfile.mtime: with self.plotLock: plotter.plotOptimizeRun(lines, plotfile, highlightmins=highlightmins) return serve_file(plotfile) @cherrypy.expose def simulatorStatsPlot(self): reload(plotter) tmpdir = self.simulator.tmpdir statsfile = tmpdir.parent.joinpath('simulator.stats') assert statsfile.exists() plotfile = tmpdir.joinpath('simulatorStats.png') lines = statsfile.lines() with self.plotLock: plotter.plotSimulatorStats(lines, plotfile) return serve_file(plotfile) @cherrypy.expose def getAPConfig(self): cherrypy.response.headers['Content-Type'] = 'text/xml' tree = StringToTree('') for apid in self.config['aps'].sections: cfg = self.config['aps'][apid] attribs = {'mac': cfg['mac'], 'x': cfg['x'], 'y': cfg['y'], 'z': cfg['z'], 'on': str(cfg['on']).lower(), 'powerid': cfg['powerid'], 'id': apid, 'power': cfg['power']} subelement(tree.getroot(), 'ap', attribs=attribs) return TreeToString(tree) @cherrypy.expose def getObjFile(self): cherrypy.response.headers['Content-Type'] = 'text/plain' return self.activeScene['objfile'].text() @cherrypy.expose def getMaterialsFile(self): cherrypy.response.headers['Content-Type'] = 'text/xml' materials = {} for matname in self.activeScene['materials'].scalars: reflect, alpha = self.activeScene['materials'][matname] materials[matname] = Brdf(reflect, alpha) return TreeToString(materialsAsXml(materials)) @cherrypy.expose def getSceneConfig(self): cherrypy.response.headers['Content-Type'] = 'text/xml' tree = StringToTree('') e = tree.getroot() e.attrib['bbox'] = ','.join(str(e) for e in self.activeScene['bbox']) e.attrib['resolution'] = ','.join(str(e) for e in self.activeScene['resolution']) e.attrib['numphotons'] = str(self.activeScene['numphotons']) e.attrib['density'] = str(self.activeScene['density']) e.attrib['name'] = self.activeScene.name e.attrib['device'] = self.config['defaultDevice'] e.attrib['alldevices'] = ','.join(self.config['knownStations']) return TreeToString(tree) @cherrypy.expose def getOptimizeSessions(self): cherrypy.response.headers['Content-Type'] = 'text/xml' tree = StringToTree('') _key = lambda x: x.files('*.txt')[0].mtime if len(x.files('*.txt')) > 0 else 0 for i, d in enumerate(reversed(sorted(self.optimizer.tmpdir.dirs(), key=_key))): f = d.joinpath('init.txt') if not f.exists(): continue initparams = dict([(e.split(':')[0], e.split(':')[1].strip()) for e in f.text().split('\n') if e.strip()]) attribs = {'name': d.name, 'dt': str(datetime.fromtimestamp(d.mtime)).split('.')[0]} attribs.update(initparams) sessionEl = subelement(tree.getroot(), 'session', attribs=attribs) mindelta = -1 f = d.joinpath('mins.txt') if f.exists(): mindata = f.text().strip().split('\n')[-1].split() e = subelement(sessionEl, 'minimum') for s in mindata[1:]: name, value = s.split(':') e.attrib[name.replace('/', '-')] = value return TreeToString(tree) def _plot2dmap(self, level, refresh, customdraw=None): reload(plotter) tmpdir = self.simulator.tmpdir plotfile = tmpdir.joinpath('2dcut_%s.png' % level) aps = {e.name: e for e in self.mac2cfg.values()} plotter.plot2DMap(self.activeScene, level, self.known_locations, aps, plotfile, refresh, customdraw) return plotfile @cherrypy.expose def plot2DMap(self, level, **kwds): plotfile = self._plot2dmap(level, 'refresh' in kwds) return serve_file(plotfile) @cherrypy.expose def plotMeasurementCoverage(self, level, apfilter=None, **kwds): plotfile = self.simulator.tmpdir.joinpath('2dcut_%s_fancy.png' % level) def customdraw(img, draw, m2d, z_cut, aps, locations): for apid, apcfg in aps.items(): if apfilter is not None and not apfilter in apid: continue for locid, loc in locations.items(): if abs(loc.z - z_cut) < 2 and abs(apcfg['z'] - z_cut) < 2: x1, y1 = m2d.meter2pixel(apcfg['x'], apcfg['y']) x2, y2 = m2d.meter2pixel(loc.x, loc.y) draw.line((x1, y1, x2, y2), fill=(64,64,64)) img.save(plotfile) self._plot2dmap(level, 'refresh' in kwds, customdraw) return serve_file(plotfile) @cherrypy.expose def getLocationsAndMeasurements(self, station, overlay_tracked='false'): cherrypy.response.headers['Content-Type'] = 'text/xml' tree = StringToTree('') locationsEl = subelement(tree.getroot(), 'locations') for locid, loc in self.known_locations.items(): attribs = {'id': locid, 'x': str(loc.x), 'y': str(loc.y), 'z': str(loc.z)} subelement(locationsEl, 'location', attribs=attribs) measurementsEl = subelement(tree.getroot(), 'measurements') for mac, apcfg in self.mac2cfg.items(): attribs = {'mac': mac, 'id': apcfg.name} apEl = subelement(measurementsEl, 'ap', attribs=attribs) measurements = self.getMeasurements(station, apcfg.name, fromMeasurementStore=overlay_tracked != 'true') if measurements is not None: for locid, measurement in measurements.items(): attribs = {'id': locid, 'avgrssi': measurement.avgrssi} subelement(apEl, 'location', attribs=attribs).text = ','.join(str(e) for e in measurement.rssis) return TreeToString(tree) @cherrypy.expose def startCollectPath(self, station, pathid): log.info('starting collecting path for station: %s and path: %s' % (station, pathid)) self.pathtrack_enabled = (station, pathid, time.time()) self.tracked_measurements[station][pathid] = defaultdict(list) self.tracked_positions[station][pathid] = [] def _getPathByRemoteTS(self, tracked_measurements): by_remotets = defaultdict(list) for (mac, ssid), values in tracked_measurements.items(): for apinfo in values: #!!! HACK for eduroam multi personalities aps = [('eduegcorr1', '00:17:df:a8:19:e'), ('edueg031', '00:17:df:a8:1b:4'), ('eduegcorr2' ,'00:17:df:a7:e8:0'), ('eduegdemo', '00:17:df:a8:19:9'), ('eduog1108', '00:17:df:a8:03:2'), ('eduog1server', '00:17:df:a7:e8:e'), ('eduog2206', '00:17:df:a8:a4:f'), ('eduog2corr', '00:17:df:a7:ea:f'), ('eduog2kitchen', '00:17:df:a7:e9:3'), ] for apid, mac_prefix in aps: if mac.startswith(mac_prefix): mac = self.config['aps'][apid]['mac'] break # kill last 5 microseconds digits ts = apinfo.remotets.replace(microsecond = apinfo.remotets.microsecond / 10**5 * 10**5) by_remotets[ts].append((mac, apinfo)) return by_remotets @cherrypy.expose def stopCollectPath(self, station, pathid, **kwds): self.pathtrack_enabled = None if 'broken' in kwds: log.warn('stopping broken measurement') return d = self.config['tmp_tracked'].joinpath(station, pathid) if not d.exists(): d.makedirs() for idx in range(1, 100): runid = '%02d' % idx if not d.joinpath('measurements_%s.txt' % runid).exists(): break self.lastCollectedPath = (station, pathid, runid) outfile = d.joinpath('measurements_%s.txt' % runid) outfileLocations = d.joinpath('locations_%s.txt' % runid) tracked_measurements = self.tracked_measurements[station][pathid] tracked_positions = self.tracked_positions[station][pathid] log.info('collecting path for station: %s and pathid: %s finished' % (station, pathid)) log.info('got %s measurements and %s location infos' % (len(tracked_measurements), len(tracked_positions))) by_remotets = self._getPathByRemoteTS(tracked_measurements) _mean = lambda l: float(sum(l)) / len(l) with open(outfile, 'w') as f: for ts, values in sorted(by_remotets.items()): mac2rssis = defaultdict(list) for mac, apinfo in values: if mac in self.mac2cfg: mac2rssis[mac].append(apinfo.rssi) s = ' '.join('%s:%s' % (self.mac2cfg[mac].name, '%.1f' % _mean(rssis)) for mac, rssis in mac2rssis.items() ) if not s: continue f.write('%s ts:%s\n' % (s, str(ts).replace(' ', 'T'))) with open(outfileLocations, 'w') as f: for locid, ts in tracked_positions: f.write('locid:%s ts:%s\n' % (locid, str(ts).replace(' ', 'T'))) @cherrypy.expose def newCollectPathPosition(self, station, pathid, locid, remotets): cherrypy.response.headers['Content-Type'] = 'text/xml' log.info('got locid: %s for station: %s and pathid: %s' % (locid, station, pathid)) self.tracked_positions[station][pathid].append((locid, datetime.fromtimestamp(float(remotets)))) return '' % len(self._getPathByRemoteTS(self.tracked_measurements[station][pathid])) def _getCollectedPathids2runids(self, station, filter=None): tree = self._getCollectedPaths(station) pathids2runids = defaultdict(list) for pathEl in tree.getroot().xpath('path'): if filter is not None and filter.endswith('_r'): filter = filter[:-2] if filter is not None and not filter in pathEl.attrib['id']: continue for runEl in pathEl.xpath('run'): pathids2runids[pathEl.attrib['id']].append(runEl.attrib['id']) return pathids2runids def _getCollectedPaths(self, station): tree = StringToTree('') for pathid in self.activeScene['paths'].scalars: locs = ','.join(str(e) for e in self.activeScene['paths'][pathid]) pel = subelement(tree.getroot(), 'path', attribs={'id': pathid, 'locations': locs}) d = self.config['tmp_tracked'].joinpath(station, pathid) if not d.exists(): continue fnames = sorted(d.files('measurements_*.txt')) for fname in fnames: id = fname.name[len('measurements_'):-4] rel = subelement(pel, 'run', attribs = {'id': id}) subelement(rel, 'measurements').text = fname.text() locfile = d.joinpath('locations_%s.txt' % id) subelement(rel, 'locations').text = locfile.text() if locfile.exists() else '' return tree @cherrypy.expose def getCollectedPaths(self, station): cherrypy.response.headers['Content-Type'] = 'text/xml' tree = evaluate.getCollectedPaths(station, self.activeScene['paths'], self.config['tmp_tracked']) return TreeToString(tree) @cherrypy.expose def plotEvaluatedPath(self, optrun, station, pathid, runid, setting, cubewidth, algo): plotfile = self.config['tmp'].joinpath('evaluator', optrun, '%s_%s_%s_%s_%s_%s.png' % (station, cubewidth, algo, setting, pathid, runid)) return serve_file(plotfile) @cherrypy.expose def interestingOptrun(self, optrun): f = self.optimizer.tmpdir.joinpath(optrun, '.interested') if f.exists(): f.remove() else: f.touch() raise cherrypy.HTTPRedirect(cherrypy.request.headers['Referer']) @cherrypy.expose def buildOptrun(self, optrun): reload(evaluate) evaluate.buildOptrun(self, optrun) raise cherrypy.HTTPRedirect(cherrypy.request.headers['Referer']) @cherrypy.expose def tiles(self, dummy, level, zoom, x, y): f = self.config['tmp'].joinpath('tiles_%s' % level, zoom, x, y) return serve_file(f, content_type="image/jpeg") @cherrypy.expose def apdatatiles(self, dummy, device_apid, zoom, x, y): device, apid = device_apid.split(',') with self.draw_aptiles_lock: d = self.config['tmp'].joinpath('apdatatiles_%s' % apid) if not d.exists(): env = self.getLocalizationEnv(device) reload(plotter) plotter.plot2DMapAPData(self, self.activeScene, env, apid) f = d.joinpath(zoom, x, y) return serve_file(f, content_type="image/jpeg") def intoMapSpace(self, x, y, z): ''' from real space (m) into map space (px)''' pixel_per_meter = self.activeScene['2dpixelpermeter'] x1, x2, y1, y2, z1, z2 = self.activeScene['bbox'] res_x = int(x * pixel_per_meter * 8 - x1 * pixel_per_meter * 8) res_y = int(y * pixel_per_meter * 8 - y1 * pixel_per_meter * 8) res_z = int(z * pixel_per_meter * 8 - z1 * pixel_per_meter * 8) return res_x, res_y, res_z def fromMapSpace(self, x, y, z): ''' from map (px) into real space (m)''' pixel_per_meter = self.activeScene['2dpixelpermeter'] x1, x2, y1, y2, z1, z2 = self.activeScene['bbox'] res_x = (x + x1 * pixel_per_meter * 8) / (pixel_per_meter * 8) res_y = (y + y1 * pixel_per_meter * 8) / (pixel_per_meter * 8) res_z = (z + z1 * pixel_per_meter * 8) / (pixel_per_meter * 8) return res_x, res_y, res_z @cherrypy.expose def locinfo(self, device, sessionid, algo, limit=100, skip=0): limit = int(limit) skip = int(skip) cherrypy.response.headers['Content-Type'] = 'application/json' env = self.getLocalizationEnv(device) queue = self.last_result[(device, sessionid, algo)] while queue.qsize() > 100: try: queue.get(False) except Queue.Empty: continue results = [] for i in range(100): try: results.append(queue.get(False)) except Queue.Empty: break #~ print len(results), (device, algo) frames = [] for j, result in enumerate(results): if skip > 0 and j % skip != 0: continue frame = {} z_count = defaultdict(int) x_best, y_best, z_best, costs, ma_best, mm_best = result[-1] hight = z_best m_best_dict = {} for apid, aprssi in zip(env.aps.keys(), ma_best): if aprssi < 0: m_best_dict[apid] = aprssi x_best, y_best, z_best = env.translateToVoxel(x_best, y_best, z_best) for i, (x, y, z, costs, ma, mm) in enumerate(result): x, y, z = self.intoMapSpace(x, y, z) z_count[z] += 1 frame[(x, y, z)] = (costs, i) top_z = list(sorted([(v, k) for k, v in z_count.items()], reverse=True))[0][1] topn_z = {} for (x, y, z), v in frame.items(): if top_z != z: continue topn_z[(x, y)] = (v[0], v[1]) topn = list(sorted(topn_z.items(), key=lambda e: e[1][1]))[-limit:] topn = [(k[0], k[1], int(v[1])) for k, v in topn] aps = env.aps.keys() apdata = [(e, env.aps[e].dbdata[x_best, y_best, z_best], m_best_dict.get(e, -100)) for e in aps] pos_x, pos_y, pos_z = self.intoMapSpace(*mm_best.pos) frames.append({'pos': (pos_x, pos_y, pos_z), 'z': top_z, 'topn': topn, 'aps': apdata, 'hight':hight}) #~ print frames return json.dumps({'data': frames, 'meta': {'queuesize': queue.qsize()}}) @cherrypy.expose def spawnSyntheticSignal(self, device, lon, lat): x, y, _ = self.fromMapSpace(float(lon), float(lat), 0) env = self.getLocalizationEnv(device) ts = time.time() _x, _y, _z = env.translateToVoxel(x, y, 1) _scandata = [] for apid, ap in env.aps.items(): bssid = self.config['aps'][apid]['mac'] rssi = ap.dbdata[_x, _y, _z] _scandata.append({'bssid': bssid, 'rssi': rssi, 'ts': ts, 'ssid': apid}) self.newData(device, _scandata) @cherrypy.expose def startLocalizeSession(self, device, sessionid): self.localizeSessions[device].add(sessionid) #TODO: need clean up strategy for self.last_result @cherrypy.expose def stopLocalizeSession(self, device, sessionid): self.localizeSessions[device].discard(sessionid) for (_device, _sessionid, algo) in self.last_result.keys(): if _device == device and _sessionid == sessionid: queue = self.last_result[(_device, _sessionid, algo)] while queue.qsize() > 0: try: queue.get(False) except Queue.Empty: continue @cherrypy.expose def useAPforLocalizeDevice(self, device, apid, use): if use == 'false': self.localizeSessionInactiveAPs[device].add(apid) else: self.localizeSessionInactiveAPs[device].discard(apid) def handleError(): '''cherrypy error handling function''' cherrypy.response.headers['Content-Type'] = 'text/plain' h = sorted(" %s: %s" % (k, v) for k, v in cherrypy.request.header_list) headers = '\nRequest Headers:\n' + '\n'.join(h) url = cherrypy.url(qs=cherrypy.request.query_string) formattedparams = ', '.join('%s: %s' % (k, v.encode('utf-8') if isinstance(v, basestring) else '?') for k, v in cherrypy.request.params.items()) params = '\nRequest Parameters:\n ' + (formattedparams if formattedparams.strip() else '') msg = '\nRequest Url:\n %s\n%s\n %s\n\n' % (url, params, headers) log.error(msg, exc_info=True) stackTrace = "".join(traceback.format_exc()) cherrypy.response.body = [''.join([msg, stackTrace, '\n'])] def check_shutdown(sig): if sig == win32con.CTRL_C_EVENT: log.warn('received CTRL-C, inititating shutdown') elif sig == win32con.CTRL_CLOSE_EVENT: log.warn('received close, inititating shutdown') elif sig in (win32con.CTRL_SHUTDOWN_EVENT, win32con.CTRL_LOGOFF_EVENT): log.warn('received logoff/shutdown, inititating shutdown') cherrypy.engine.stop() cherrypy.engine.exit() print 'LWS SHUTDOWN DONE' def startServer(configfile=None, blocking=True): setupLogging() if sys.platform == 'win32': win32api.SetConsoleCtrlHandler(check_shutdown, True) if configfile is None: configfile = path('./lws.ini').abspath() if not path('./lws.ini').exists(): print 'no configfile given - autocreate at %s' % configfile.abspath() configfile.touch() else: configfile = path(configfile).abspath() global app app = LWS(configfile) globalconf = { 'server.thread_pool' : 50, 'server.socket_host' : app.config['httpHost'], 'server.socket_port' : app.config['httpPort'], 'engine.autoreload_on' : app.config['cpAutoRestart'], 'log.screen' : app.config['cpLogScreen'], 'tools.sessions.on' : True, 'tools.sessions.storage_type' : 'ram', 'checker.on' : False, # disable cherrypy checker 'tools.decode.on' : True, # decode with utf-8 - fallback to latin-1 'tools.gzip.on' : True, 'tools.gzip.compress_level' : 6, #gzip these mimetypes 'tools.gzip.mime_types' : ['text/html', 'text/plain', 'text/css', 'application/x-javascript', 'text/javascript', 'text/xml', 'application/javascript', 'application/json', ], 'tools.proxy.on' : app.config['behindProxy'], 'tools.proxy.base' : app.config['proxyBase'], 'tools.encode.on' : True, 'tools.encode.encoding' : 'utf-8', 'request.error_response' : handleError, } cherrypy.config.update(globalconf) appconf = {} appconf['/static'] = { 'tools.etags.on' : True, 'tools.etags.autotags' : True, 'tools.staticdir.on' : True, 'tools.staticdir.dir' : PATH_STATIC, 'tools.sessions.on' : False, } cptree = cherrypy.tree.mount(app, '/', config=appconf) log.info('mounting server at %s:%s' % (app.config['httpHost'], app.config['httpPort'])) cherrypy.engine.start() if blocking: cherrypy.engine.block() def stopServer(): cherrypy.engine.exit()