From 9191312aac429e745388029ba57a29f015378541 Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Thu, 20 Jun 2013 19:25:15 +0200 Subject: Add envmon code --- envmon/colorgen.py | 51 +++++++++++++++++++++++++++++++++++++++ envmon/draw-graph.py | 49 ++++++++++++++++++++++++++++++++++++++ envmon/onewire-ow.py | 46 ++++++++++++++++++++++++++++++++++++ envmon/onewire-sensors.txt | 8 +++++++ envmon/onewire-sysfs.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ envmon/onewire.py | 1 + envmon/onewire2rrd.py | 18 ++++++++++++++ envmon/outdoor2rrd.py | 21 +++++++++++++++++ envmon/rrdlog.py | 13 ++++++++++ envmon/weather.py | 24 +++++++++++++++++++ 10 files changed, 290 insertions(+) create mode 100644 envmon/colorgen.py create mode 100755 envmon/draw-graph.py create mode 100644 envmon/onewire-ow.py create mode 100644 envmon/onewire-sensors.txt create mode 100644 envmon/onewire-sysfs.py create mode 120000 envmon/onewire.py create mode 100755 envmon/onewire2rrd.py create mode 100755 envmon/outdoor2rrd.py create mode 100644 envmon/rrdlog.py create mode 100644 envmon/weather.py diff --git a/envmon/colorgen.py b/envmon/colorgen.py new file mode 100644 index 0000000..0d0e973 --- /dev/null +++ b/envmon/colorgen.py @@ -0,0 +1,51 @@ +import colorsys +import random + +class ColorGen(): + def __init__(self, seed=0): + self.random = random.Random() + self.random.seed(seed) + self.color_list = [(1.0,1.0,1.0)] + + def rate(self, color): + distances = [] + for other_color in self.color_list: + distances.append(self.distance(color, other_color)) + return self.get_score(distances) + + def get_score(self, distances): + return min(distances) + + def distance(self, a, b): + return ((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2) ** 0.5 + + def mutate(self, candidate): + rv = [0,0,0] + for i in range(0,2): + rv[i] = candidate[i] + self.random.uniform(-0.1,0.1) + if rv[i] < 0: + rv[i] = 0.0 + if rv[i] > 1: + rv[i] = 1.0 + return tuple(rv) + + def get_next(self): + candidates = [] + for i in range(0, 50): # Create 50 candidates + candidates.append(( + self.random.uniform(0,1), + self.random.uniform(0,1), + self.random.uniform(0,1) + )) + + for r in range(0, 40): # Do 40 Rounds + for c in list(candidates): + for i in range(0,5): # Create 5 decendants of each candidate + candidates.append(self.mutate(c)) + # Keep the best ten candidates + candidates.sort(key=lambda candidate:-self.rate(candidate)) + candidates = candidates[:10] + + # And the winner is: + self.color_list.append(candidates[0]) + return candidates[0] diff --git a/envmon/draw-graph.py b/envmon/draw-graph.py new file mode 100755 index 0000000..4e9d637 --- /dev/null +++ b/envmon/draw-graph.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +import colorgen +import rrdtool +import time +import onewire +import sys + +dir_prefix = '/home/nihilus/envmon/' + +datasets = sorted([ (sensor, 'onewire-temp-%s.rrd' % sensor) for sensor in onewire.sensors() ], key = lambda x:x[0]) +datasets += [ ('Outside', 'env-outside.rrd') ] + +def graph(name, duration): + print >>sys.stderr, "Graphing %s" % name + end_struct = time.localtime(time.time() - 450) + end = time.mktime(end_struct) - end_struct.tm_sec - 60 * (end_struct.tm_min % 5) + + prefix = [ + name, + '-E', + '--end', '%d' % end, + '--start', 'end-%ds' % duration, + '--width', '800', + '--height', '300' + ] + + body = [] + cg = colorgen.ColorGen() + for dataset_name, dataset_rrd in datasets: + body.append('DEF:temperature-%(name)s=%(filename)s:temperature:AVERAGE:start=end-%(duration)d' % { + 'name': dataset_name, + 'filename': dir_prefix + dataset_rrd, + 'duration': duration + 3600 + }) + color = cg.get_next() + color = (int(color[0] * 255), int(color[1] * 255), int(color[2] * 255)) + body.append('LINE2:temperature-%(name)s#%(r)02x%(g)02x%(b)02x:%(name)s' % { + 'name': dataset_name, + 'r': color[0], + 'g': color[1], + 'b': color[2] + }) + rrdtool.graph(prefix + body) + print >>sys.stderr, "Done with %s" % name + +graph('env-day.png', 86400) +graph('env-week.png', 604800) +graph('env-month.png', 2592000) +graph('env-year.png', 31557600) diff --git a/envmon/onewire-ow.py b/envmon/onewire-ow.py new file mode 100644 index 0000000..6e3420b --- /dev/null +++ b/envmon/onewire-ow.py @@ -0,0 +1,46 @@ +""" +Onewire interface using owserver +""" + +import os +import ow + +class OnewireException(Exception): + pass + +ow.init('127.0.0.1:4304') + +_sensors = {} +with open(os.path.join(os.path.realpath(os.path.dirname(__file__)), 'onewire-sensors.txt'), 'r') as sensors_file: + for line in sensors_file: + line = line.strip() + address, name = line.split(' ', 1) + _sensors[name] = address + +def sensors(): + return list(_sensors.keys()) + +class SensorFacade(object): + def __init__(self, sensor, name): + self._sensor = sensor + self._name = name + + def __getattr__(self, name): + return getattr(self._sensor, name) + + @property + def temperature(self): + for i in range(1,3): + rv = float(self._sensor.temperature) + if rv > 65: + continue + if rv < -25: + continue + return rv + raise OnewireException("Bus error for %s" % self._name) + +def sensor(name): + if name not in _sensors: + raise OnewireException('Don\'t know about sensor %s' % name) + + return SensorFacade(ow.Sensor('/%s' % _sensors[name]), name) diff --git a/envmon/onewire-sensors.txt b/envmon/onewire-sensors.txt new file mode 100644 index 0000000..86771e3 --- /dev/null +++ b/envmon/onewire-sensors.txt @@ -0,0 +1,8 @@ +10.0008020BB209 hacklab +10.0008020B772C rack +10.0008020BB966 office +10.0008020BAC81 clockwork +10.0008020BA18F workshop +28.0000031E4328 lounge +28.0000031E74F8 stage +28.0000031E3C90 test diff --git a/envmon/onewire-sysfs.py b/envmon/onewire-sysfs.py new file mode 100644 index 0000000..20f3fde --- /dev/null +++ b/envmon/onewire-sysfs.py @@ -0,0 +1,59 @@ +""" +Onewire interface using w1 sysfs +""" + +import os +import sys +import time + +class OnewireException(Exception): + pass + +_sensors = {} +with open(os.path.join(os.path.realpath(os.path.dirname(__file__)), 'onewire-sensors.txt'), 'r') as sensors_file: + for line in sensors_file: + line = line.strip() + address, name = line.split(' ', 1) + _sensors[name] = address + +def sensors(): + return list(_sensors.keys()) + +class SensorFacade(object): + def __init__(self, addr, name): + self._addr = addr + self._name = name + + def get_temperature(self): + addr = self._addr.replace('.', '-').lower() + path = '/sys/devices/w1_bus_master1/{0}/w1_slave'.format(addr) + + with open(path, 'r') as w1_file: + w1_data = w1_file.read() + + match = re.search(r'\st=(\d+)', w1_data) + temp = float(match.group(1)) / 1000 + return temp + + @property + def temperature(self): + for i in range(1,3): + try: + rv = self.get_temperature() + except Exception: + sys.excepthook(*sys.exc_info()) + time.sleep(0.5) + continue + + if rv > 65: + continue + if rv < -25: + continue + return rv + raise OnewireException("Bus error for %s" % self._name) + +def sensor(name): + if name not in _sensors: + raise OnewireException('Don\'t know about sensor %s' % name) + + return SensorFacade('%s' % _sensors[name], name) diff --git a/envmon/onewire.py b/envmon/onewire.py new file mode 120000 index 0000000..32460f9 --- /dev/null +++ b/envmon/onewire.py @@ -0,0 +1 @@ +onewire-sysfs.py \ No newline at end of file diff --git a/envmon/onewire2rrd.py b/envmon/onewire2rrd.py new file mode 100755 index 0000000..d3d2927 --- /dev/null +++ b/envmon/onewire2rrd.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import time +import sys + +import onewire +import rrdlog + +while True: + for sensor in onewire.sensors(): + try: + rrdlog.TempLog('onewire-temp-%s.rrd' % sensor).update(onewire.sensor(sensor).temperature) + except Exception: + print >>sys.stderr, "On %s: Could not retrieve temperature for '%s':" % ( + time.strftime('%a, %d %b %Y %T %z'), sensor) + sys.excepthook(*sys.exc_info()) + print >>sys.stderr, '========================================' + time.sleep(300-len(onewire.sensors())) diff --git a/envmon/outdoor2rrd.py b/envmon/outdoor2rrd.py new file mode 100755 index 0000000..1c4ca24 --- /dev/null +++ b/envmon/outdoor2rrd.py @@ -0,0 +1,21 @@ +#!/usr/bin/python + +import time +import sys + +import rrdlog +import weather + +location = '20065491' # Leipzig + +while True: + try: + data = weather.weather(location) + rrdlog.TempLog('env-outside.rrd').update(data.temperature) + except Exception: + sys.stderr.write('Exception occured at %s:\n' % time.strftime('%a, %d %b %Y %T %z')) + sys.excepthook(*sys.exc_info()) + sys.stderr.write('========================================\n') + time.sleep(60) + else: + time.sleep(300) diff --git a/envmon/rrdlog.py b/envmon/rrdlog.py new file mode 100644 index 0000000..4dd4708 --- /dev/null +++ b/envmon/rrdlog.py @@ -0,0 +1,13 @@ +import rrdtool +import os + +class TempLog(object): + def __init__(self, name): + self.name = name + if not os.path.exists(self.name): + rrdtool.create(self.name, 'DS:temperature:GAUGE:600:U:U', + 'RRA:AVERAGE:0.5:1:17280', # Keep 5min snapshots for the last two months + 'RRA:AVERAGE:0.5:12:87660', # Keep 1h averages for 10 years (as if...) + ) + def update(self, temperature): + rrdtool.update(self.name, 'N:%f' % temperature) diff --git a/envmon/weather.py b/envmon/weather.py new file mode 100644 index 0000000..c5396c6 --- /dev/null +++ b/envmon/weather.py @@ -0,0 +1,24 @@ +import contextlib +import urllib2 +import xml.etree.ElementTree + +class WeatherData(object): + def __init__(self): + pass + +def weather(woeid): + """ + Takes a woeid as argument (see yahoo api docs) and returns a dictionary + containing weather data. + """ + + with contextlib.closing(urllib2.urlopen('http://weather.yahooapis.com/forecastrss?w=%s&u=c' % woeid, timeout=2.0)) as request: + tree = xml.etree.ElementTree.fromstring(request.read()) + + yweather = 'http://xml.weather.yahoo.com/ns/rss/1.0' + results = WeatherData() + + results.temperature = float(tree.find('.//{%s}condition' % yweather).get('temp')) + results.humidity = float(tree.find('.//{%s}atmosphere' % yweather).get('humidity')) + + return results -- cgit v1.2.1