Wetterstation in die Visualisierung einbinden

Da viele Funktionen im und am Haus vom Wetter abhängig sind, haben wir überlegt, wie wir die Wetterdaten am besten bekommen können. Die eine Überlegung war es, aus dem Internet die entsprechenden Daten zu parsen. Leider sind die Daten doch recht ungenau, da die nächste offizielle Wetterstation sehr weit entfernt ist. Also musste es eine eigene Wetterstation sein. Die Anforderung war dabei, dass wir sie in unser Eco-System einbinden können.

Wetterstation Renkforce WH2600

Nach einiger Recherche sind wir auf die Wetterstation Renkforce WH2600 von Conrad gestoßen. Diese bringt einen kleinen Empfänger mit, der eine Webseite mit den Wetterdaten bereitstellt. das klang nach einer einfachen Möglichkeit an die Wetterdaten zu kommen.

Wetterstation im Garten aufgebaut

Die Wetterstation wurden also im Garten aufgebaut und die kleine Empfangsstation an Netzwerk im Haus angebunden. Sofort konnten wir uns die Wetterdaten ansehen, die auf der internen Webseite zur Verfügung standen.

Update der Firmware

Leider war die ausgelieferte Firmware nicht allzu stabil. Vor allem fehlte ihr aber die Anbindung an WeatherUnderground, welches wir auch gerne anbinden wollten. Deswegen haben wir recherchiert und herausgefunden, dass die Wetterstation auch mit der Firmware von Ambient Weather kompatibel sein soll. Also haben wir es gewagt und das Update eingespielt. Die folgende Anleitung bezieht sich dabei auf die Firmware 4.5.8.

Daten auslesen und in die Datenbank speichern

Die Wetterstation stellt eine Webseite zur Verfügung die wie folgt aussieht:

Webseite der Wetterstation

Wie man sehen kann, werden die Daten in schreibgeschützen Input-Feldern dargestellt. Mit der Untersuchen Funktion des Browsers kann man sehr einfach den Namen der Felder herausfinden. So hat das Feld „Wind Direction“ den Namen „windir„.

Diese Information hilft uns, einen kleinen Parser zu schreiben, der alle Felder ausliest. Als erstes parsen wir dazu die Webseite mit Hilfe von urllib.

import urllib.request
URL_FOR_DATA = 'http://192.168.178.251/livedata.htm'
  
with urllib.request.urlopen(URL_FOR_DATA) as response:
    html = response.read()

Weiterhin benötigen wir für jedes Feld eine Regular Expression um es auslesen zu können. Das sieht dann ungefähr so aus:

regex_temp = re.compile('<input name="outTemp".*?value="(.*?)".*?>')

Eine kleine Hilfsfunktion hilft uns dabei die Daten zu parsen.

 def _get_value(self, text, regex):
    match = re.search(regex, str(text))
    if match:
        if match.group(1) in ('----', '--'):
            return -99
        return match.group(1)
    else:
        return 0

Damit können wir jetzt die HTML Seite durchsuchen und den gefunden Wert parsen.

temp = _get_value(html, regex_temp)

Damit ist das schwerste schon erledigt. Denn so kommen wir einfach an alle Daten der Wetterstation. Jetzt liegen noch zwei Aufgaben vor uns: Zum einen wollen wir die Daten visualisieren und zum Anderen wollen wir sie regelmäßig für eine Statistik in die Datenbank packen.

Daten der Wetterstation in die Datenbank schreiben

Als erstes schreiben wir die Daten in die Datenbank. Dazu nutzen wir die Mechanismen von Django aus. Dazu benötigen wir als erstes ein Model, dass die Struktur der Datenbank abbildet. Das Model sieht wie folgt aus:

from django.db import models

class WeatherData(models.Model):
    timestamp = models.DateTimeField(editable=False, primary_key=True)
    absolute_pressure = models.FloatField(editable=False)
    relative_pressure = models.FloatField(editable=False)
    temperature = models.FloatField(editable=False)
    humidity = models.FloatField(editable=False)
    wind_direction = models.FloatField(editable=False)
    wind_speed = models.FloatField(editable=False)
    wind_gust = models.FloatField(editable=False)
    solar_radiation = models.FloatField(editable=False)
    uv = models.FloatField(editable=False)
    uv1 = models.FloatField(editable=False)
    hourly_rain_rate = models.FloatField(editable=False)
    daily_rain_rate = models.FloatField(editable=False)

Jetzt können wir Daten in die Datenbank packen, indem wir den Save-Befehl für das Datenobjekt verwenden,. Das sieht dann wie folgt aus:

from homeautomation.outdoor_weather.models import WeatherData


data = WeatherData(timestamp=timezone.now(), temperature=temp, absolute_pressure=abs_press,
                           relative_pressure=rel_press, humidity=hum, wind_direction=wind_dir,
                           wind_speed=wind_speed, wind_gust=wind_gust, solar_radiation=solar,
                           uv=uv, uv1=uv1, hourly_rain_rate=rain_hour, daily_rain_rate=rain_day)
data.save()

Damit fehlt nur noch der Teil, diese Aufgabe regelmäßig zu tun. Für Django gibt es ein kleines AddOn, das diese Aufgabe übernehmen kann. Die Bilbliothek heißt django_cron. Mit dieser Bibliothek kann man sehr einfach Cron-Jobs in Django steuern.

Dazu sollte als erstes die settings.py um zwei Einträge erweitert werden. Der erste Eintrag dient dazu, dass unsere Datenbank nicht überfüllt, indem die Logs nach 3 Tagen gelöscht werden. Der zweite Eintrag meldet unsere Cron-Klasse an.

DJANGO_CRON_DELETE_LOGS_OLDER_THAN = 3

CRON_CLASSES = [
    "homeautomation.crons.weather.WeatherJob",
]

Nun muss natürlich noch die Cron-Klasse hinzugefügt werden. Diese besteht aus den oben beschriebenen Funktionen. Zusätzlich wird über die Parameter gesteuert, wie häufig der Job laufen soll.

from django_cron import CronJobBase, Schedule
from django.utils import timezone

from homeautomation.outdoor_weather.models import WeatherData

import urllib.request
import re

class WeatherJob(CronJobBase):
    RUN_EVERY_MINS = 1
    ALLOW_PARALLEL_RUNS = False

    schedule = Schedule(run_every_mins=RUN_EVERY_MINS)
    code = 'homeautomation.WeatherJob'

    URL_FOR_DATA = 'http://192.168.178.251/livedata.htm'

    def _get_value(self, text, regex):
        match = re.search(regex, str(text))
        if match:
            if match.group(1) in ('----', '--'):
                return -99
            return match.group(1)
        else:
            return 0

    def parse_url(self):
        with urllib.request.urlopen(self.URL_FOR_DATA) as response:
            html = response.read()
            return html

    def do(self):
        text = self.parse_url()
        regex_temp = re.compile('<input name="outTemp".*?value="(.*?)".*?>')
        regex_abs = re.compile('<input name="AbsPress".*?value="(.*?)".*?>')
        regex_rel = re.compile('<input name="RelPress".*?value="(.*?)".*?>')
        regex_hum = re.compile('<input name="outHumi".*?value="(.*?)".*?>')
        regex_winddir = re.compile('<input name="windir".*?value="(.*?)".*?>')
        regex_windspeed = re.compile('<input name="windspeed".*?value="(.*?)".*?>')
        regex_windgust = re.compile('<input name="gustspeed".*?value="(.*?)".*?>')
        regex_solar = re.compile('<input name="solarrad".*?value="(.*?)".*?>')
        regex_uv = re.compile('<input name="uv".*?value="(.*?)".*?>')
        regex_uv1 = re.compile('<input name="uvi".*?value="(.*?)".*?>')
        regex_hourrain = re.compile('<input name="rainofhourly".*?value="(.*?)".*?>')
        regex_dailyrain = re.compile('<input name="rainofdaily".*?value="(.*?)".*?>')

        temp = self._get_value(text, regex_temp)
        abs_press = self._get_value(text, regex_abs)
        rel_press = self._get_value(text, regex_rel)
        hum = self._get_value(text, regex_hum)
        wind_dir = self._get_value(text, regex_winddir)
        wind_speed = self._get_value(text, regex_windspeed)
        wind_gust = self._get_value(text, regex_windgust)
        solar = self._get_value(text, regex_solar)
        uv = self._get_value(text, regex_uv)
        uv1 = self._get_value(text, regex_uv1)
        rain_hour = self._get_value(text, regex_hourrain)
        rain_day = self._get_value(text, regex_dailyrain)

        data = WeatherData(timestamp=timezone.now(), temperature=temp, absolute_pressure=abs_press,
                           relative_pressure=rel_press, humidity=hum, wind_direction=wind_dir,
                           wind_speed=wind_speed, wind_gust=wind_gust, solar_radiation=solar,
                           uv=uv, uv1=uv1, hourly_rain_rate=rain_hour, daily_rain_rate=rain_day)
        data.save()

Wichtig ist es jetzt nur noch, dass der Cron-Job auch regelmäßig getriggert wird. Dazu auf dem Server am besten in die Crontabs der Befehl manage.py runcrons mit den entsprechenden Pfaden eingebaut. Am besten liest man dazu die Dokumentation von django-crons und schaut sich an, wie solche Aufgaben auf dem eigenen Betriebssystem gestartet werden.

Der Befehl manage.py runcrons hilft aber auch dabei, den eigenen Cron-Job zu testen.

Jetzt sind die aktuellen Daten in der Datenbank. Nun müssen wir sie nur noch darstellen.

Visualisierung der Daten der Wetterstation

Die Daten sollen nun auf den Displays dargestellt werden. Dazu lesen wir aus der Datenbank die letzten Daten aus und übergeben diese an das Template was die Daten darstellt. Das Ergebnis sieht dann so aus:

Als erstes werden also die Daten in Django im View ausgegeben und ans Template weitergereicht. Dabei formatieren wir die Daten, vor allem splitten wir die Daten auf, um an die Kommastellen zu kommen.

class WeatherOverview(View):
    def get(self, request):

        data = WeatherData.objects.latest('timestamp')

        context = dict()
        context['data'] = data
 
        temp = data.temperature
        if temp < 0:
            temp = abs(temp)
            sign = -1
        else:
            sign = 1
        context['temperature'] = sign * math.floor(temp), temp % 1
        
        # mehr Daten für den Context formatieren
        return render(request, 'weather.html', context)

Das Ganze wird dann im Template wie folgt verarbeitet:

    <a href="/outdoor_weather/tempstat/">
        <div class="temp">
            <div class="big">
                <span class="right">
                    {{temperature.0}}.
                </span>
            </div>
            <div class="big">
                <div class="small">°C</div>
                <div class="small">{{temperature.1|floatformat:2|cut:"0,"}}</div>
            </div>
        </div>
    </a>

Aus diesen Blöcken mit etwas CSS und netten Bildern aus dem KNX UI Iconset lassen sich dann die oben gezeigte Seite bauen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.