und noch ein Wetter-Raspi

Da unsere beiden Funkwetterstationen inzwischen diverse Ausfallerscheinungen zeigen (Außensensor nicht erkannt, tw. falsche Zeit, …), habe ich überlegt mir eine eigene Wetterstation zu basteln.
Dabei wollte ich verschiedene Datenquellen kombinieren und konnte auf bereits vorhandene Teilsysteme zurückgreifen.
Schon seit längerem habe ich einen USB-Datenlogger mit Kombisensor für Außentemperatur und -luftfeuchte am Laufen. Leider gibt es keine kompatiblen Sensoren mehr dafür zu kaufen. Deshalb kann ich die angedachte Erweiterung um einen Regen- und Windsensor nicht mehr umsetzen.
Für die Innentemperatur und den Luftdruck nutze ich gleichfalls schon länger Komponenten von tinkerforge. Die Übertragung erfolgt hier via WLAN.
Anfangs habe ich die empfangenen Daten in eine MySQL-DB geschrieben. Das wurde aber auf Dauer bissel viel, so dass ich irgendwann auf influxdb umgestiegen bin.
Das Dashboard ist dann mit grafana zusammengebastelt.

Die lokale Datenerfassung läuft auf einem intel NUC mit debian, wo auch mein Server-Monitoring mit check_mk läuft (hiermit überwache ich u.a. meine vServer, mein NAS mit openmediavault und meine Sophos-Firewall). Aber ich schweife ab…

Grob sieht die Systemarchitektur wie folgt aus:

Folgende Daten sind lokal erhoben (auf dem NUC):

  • Temperatur außen (incl. Min- + Max-Werte sowie Verlauf der letzten 7 Tage)
  • Innentemperatur aktuell
  • Luftdruckverlauf

Die anderen Werte (5Tages-Vorhersage Temperatur und Regen, aktuelle Regenstärke, Wind) und Bewölkungsbildchen stammen aus der openweatermap-API und werden direkt auf dem Raspi erstellt.

Programmiert ist alles mit Python3. Für die Aneinanderreihung der Vorschau-Icons nutze ich das tool montage aus dem ImageMagick Paket. Die Icons habe ich dazu separat runtergeladen, die Icon-ID kommt über die API und steht in der influxdb.

Und so sieht das derzeitige Ergebnis aus:

Durch das Raster-Layout von grafana ist das 7″ Touch-Display des Raspi sehr schnell “voll”. Also muss ich mich auf die wichtigsten Sachen beschränken. Wahrscheinlich fliegen Wind und aktuelle Regenmenge wieder raus. Mal sehen.

Code für die lokale Datenerfassung (tinkerforge und ELV):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'fs'


import logging
import logging.handlers
import argparse
import sys
import time
import datetime
#: install pyserial, nicht serial!
import serial

from influxdb import InfluxDBClient

from tinkerforge.ip_connection import IPConnection
from tinkerforge.brick_master import Master
import tinkerforge.bricklet_temperature
import tinkerforge.bricklet_humidity
import tinkerforge.bricklet_barometer

# Deafults
LOG_FILENAME = "/var/log/tf/collectweatherdata.log"
LOG_LEVEL = logging.DEBUG # Could be e.g. "DEBUG" or "WARNING"

# Define and parse command line arguments
parser = argparse.ArgumentParser(description="service for inserting tinkerforge bricklet data to influxdb")
parser.add_argument("-l", "--log", help="file to write log to (default '" + LOG_FILENAME + "')")

# If the log file is specified on the command line then override the default
args = parser.parse_args()
if args.log:
        LOG_FILENAME = args.log

#### Logging
# Configure logging to log to a file, making a new file at midnight and keeping the last 3 day's data
# Give the logger a unique name (good practice)
logger = logging.getLogger(__name__)
# Set the log level to LOG_LEVEL
logger.setLevel(LOG_LEVEL)
# Make a handler that writes to a file, making a new file at midnight and keeping 3 backups
handler = logging.handlers.TimedRotatingFileHandler(LOG_FILENAME, when="midnight", backupCount=3)
# Format each log message like this
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
# Attach the formatter to the handler
handler.setFormatter(formatter)
# Attach the handler to the logger
logger.addHandler(handler)

# Make a class we can use to capture stdout and sterr in the log
class MyLogger(object):
        def __init__(self, logger, level):
                """Needs a logger and a logger level."""
                self.logger = logger
                self.level = level

        def write(self, message):
                # Only log if there is a message (not just a new line)
                if message.rstrip() != "":
                        self.logger.log(self.level, message.rstrip())

        def flush(self):
                pass


# Replace stdout with logging to file at INFO level
sys.stdout = MyLogger(logger, logging.INFO)
# Replace stderr with logging to file at ERROR level
sys.stderr = MyLogger(logger, logging.ERROR)


step = 60

#elv_
elvport = serial.Serial('/dev/ttyUSB0',9600,timeout = 2);

# Hoehe ueber NN fuer LE:
altitude_loc = 114
tg = 0.0065

# Korrekturfaktoren Temperatur + Feuchte
cft1 = 0.9
cft2 = 0.95
cfh1 = 1.16
cfh2 = 1.25

# initiale definition
temp_location = 20.005


#influxdb
dbclient = InfluxDBClient(host='localhost', port=8086)
dbclient.switch_database('fswetter')


#  tinkerforge

# bricks:
tfbrick01_opts = {
        'host': "192.168.x.y1",
        'port': 4223,
        'uid_master': "m1",
        'uid_tempbricklet': "nm5",
        'uid_humbricklet': "nHB",
        'uid_pressbricklet': "qFi"
}

tfbrick02_opts = {
        'host': "192.168.x.y2",
        'port': 4223,
        'uid_master': "m2",
        'uid_tempbricklet': "nqw",
        'uid_humbricklet': "nyD"
}

ipconb1 = IPConnection() # Create IP connection
ipconb2 = IPConnection() # Create IP connection

# Create device object
mb1 = Master(tfbrick01_opts['uid_master'],ipconb1)
bt1 = tinkerforge.bricklet_temperature.Temperature(tfbrick01_opts['uid_tempbricklet'], ipconb1)
bh1 = tinkerforge.bricklet_humidity.Humidity(tfbrick01_opts['uid_humbricklet'], ipconb1)
bp1 = tinkerforge.bricklet_barometer.Barometer(tfbrick01_opts['uid_pressbricklet'], ipconb1)

mb2 = Master(tfbrick02_opts['uid_master'],ipconb2)
bt2 = tinkerforge.bricklet_temperature.Temperature(tfbrick02_opts['uid_tempbricklet'], ipconb2)
bh2 = tinkerforge.bricklet_humidity.Humidity(tfbrick02_opts['uid_humbricklet'], ipconb2)


print('======================================')
print("Start-Time: " + str(time.time()))

# Callback for temperature
def cbt1():
        try:
                temperature1 = bt1.get_temperature()
                print('temp1 raw: ' + str(temperature1/100.0) + ' °C.')
                vt1 = temperature1/100.0*cft1
                print('temp1 cor: ' + str(vt1) + ' °C.')
                global temp_location
                temp_location = vt1
                print("temp_location: "+str(temp_location))

                json_body = [
                        {
                                "measurement": "Temperatur",
                                "tags": {
                                        "location": "innen",
                                        "room": "Wohnstube"
                                },
                                "fields": {
                                        "value": vt1
                                }
                        }
                ]

        except:
                e,v,tb = sys.exc_info()
                print('error get_temp1')
                print(e)
                print(v)
        else:
                try:
                        dbclient.write_points(json_body)
                except:
                        print('influx error temp1')

def cbt2():
        try:
                temperature2 = bt2.get_temperature()
                print('temp2: ' + str(temperature2/100.0) + ' °C.')
                vt2 = temperature2/100.0*cft2
                print('temp2 cor: ' + str(vt2) + ' °C.')

                json_body = [
                        {
                                "measurement": "Temperatur",
                                "tags": {
                                        "location": "innen",
                                        "room": "Schlafstube"
                                },
                                "fields": {
                                        "value": vt2
                                }
                        }
                ]

        except:
                e,v,tb = sys.exc_info()
                print('error get_temp2')
                print(e)
                print(v)
        else:
                try:
                        dbclient.write_points(json_body)
                except:
                        print('influx error temp2')


# Callback for humidity
def cbh1():
        try:
                humidity1 = bh1.get_humidity()
                print('hum1:  ' + str(humidity1/10.0) + ' %.')
                vh1 = humidity1/10.0*cfh1
                print('hum1 cor: ' + str(vh1) + ' %.')

                json_body = [
                        {
                                "measurement": "Luftfeuchte",
                                "tags": {
                                        "location": "innen",
                                        "room": "Wohnstube"
                                },
                                "fields": {
                                        "value": vh1
                                }
                        }
                ]

        except:
                e,v,tb = sys.exc_info()
                print('error get_hum1')
                print(e)
                print(v)
        else:
                try:
                        dbclient.write_points(json_body)
                except:
                        print('influx error hum1')


def cbh2():
        try:
                humidity2 = bh2.get_humidity()
                print('hum2:  ' + str(humidity2/10.0) + ' %.')
                vh2 = humidity2/10.0*cfh2
                print('hum2 cor: ' + str(vh2) + ' %.')

                json_body = [
                        {
                                "measurement": "Luftfeuchte",
                                "tags": {
                                        "location": "innen",
                                        "room": "Schlafstube"
                                },
                                "fields": {
                                        "value": vh2
                                }
                        }
                ]

        except:
                e,v,tb = sys.exc_info()
                print('error get_hum2')
                print(e)
                print(v)
        else:
                try:
                        dbclient.write_points(json_body)
                except:
                        print('influx error hum2')


# Callback for air pressure
def cbp1():
        global temp_location
        try:
                air_pressure = bp1.get_air_pressure()
                print('press1:  ' + str(air_pressure/1000.0) + ' mbar (raw).')
                print('localtemp: ' + str(temp_location))

#               temp_nn = temp_location + tg * altitude_loc
#    value = air_pressure/1000.0/(1-tg * altitude_loc/temp_nn)**(0.03416/tg)
                value = air_pressure/1000.0/(1 - tg * altitude_loc / (273.15 + temp_location + tg * altitude_loc)) ** (0.034163 / tg)
                value = round(value,1)

                print('press1:  ' + str(value) + ' mbar (QFF).')

                json_body = [
                        {
                                "measurement": "Luftdruck",
                                "tags": {
                                        "location": "aussen"
                                },
                                "fields": {
                                        "value": value
                                }
                        }
                ]

        except:
                e,v,tb = sys.exc_info()
                print('error get_press1')
                print(e)
                print(v)
        else:
                try:
                        dbclient.write_points(json_body)
                except:
                        print('influx error press1')


def getelv():
        try:
                bline = elvport.readline()
                sline = bline.decode()
        except:
                print('error elv readline')
        else:
                if len(sline) > 0:
                        print("elv-input:" + sline)

                        values = sline.split(";")
                        if values[0] == "$1":
                                if values[3] == "":
                                        v3 = "0,0"
                                else:
                                        v3 = values[3]
                                if values[1] == "":
                                        v11 = "0,0"
                                else:
                                        v11 = values[11]

                                print("elv: "+v3+" °C ; "+v11+" %")

                                if v3:
                                        t1 = float(v3.replace(",","."))

                                        json_body = [
                                                {
                                                        "measurement": "Temperatur",
                                                        "tags": {
                                                                "location": "aussen"
                                                        },
                                                        "fields": {
                                                                "value": t1
                                                        }
                                                }
                                        ]

                                        try:
                                                dbclient.write_points(json_body)
                                        except:
                                            print('influx error t aussen')

                                if v11:
                                        h1 = float(v11.replace(",","."))

                                        json_body = [
                                                {
                                                        "measurement": "Luftfeuchte",
                                                        "tags": {
                                                                "location": "aussen"
                                                        },
                                                        "fields": {
                                                                "value": h1
                                                        }
                                                }
                                        ]

                                        try:
                                                dbclient.write_points(json_body)
                                        except:
                                                print('influx error h aussen')
                        else:
                                print('elv-input[0] is empty ($1)')


try:
        ipconb1.connect(tfbrick01_opts['host'], tfbrick01_opts['port']) # Connect to brickd
        mb1.disable_status_led()
except:
        print("Connection error to brick01")

try:
        ipconb2.connect(tfbrick02_opts['host'], tfbrick02_opts['port']) # Connect to brickd
        mb2.disable_status_led()
except:
        print("Connection error to brick02")


#next_call_time = time.time()
while True:

        cbt1()
        cbh1()
        cbt2()
        cbh2()
        cbp1()
        getelv()

#       if temp_location != 20.005: cbp1()

#       next_call_time += 1
        time.sleep(60)

        #raw_input('Press key to exit\n') # Use input() in Python 3


ipconb1.disconnect()
ipconb2.disconnect()

Code für openweathermap:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'fs'

from influxdb import InfluxDBClient
import datetime
import logging
import logging.handlers
import requests
import json
import subprocess
import os
import sys


# --- Logging
logging.basicConfig(format='%(asctime)s %(message)s', filename='/var/log/weather/owm_forecast.log',level=logging.INFO)

# --- Defaults
openweathermap_api_key = 'xxxxxxxxxxxxxxxxxxxxxx'
openweathermap_city_id = '6548737'
openweathermap_api_url = 'http://api.openweathermap.org/data/2.5/forecast?id=' + openweathermap_city_id + '&appid=' + openweathermap_api_key + '&units=metric&lang=de'

def get_rain_level_from_weather(weather):
        rain_level = 0
        if len(weather) > 0:
                for w in weather:
                        description = w['description']
                        condition_icon = w['icon']
                        condition_id = w['id']
                        cid = int(condition_id)
                        # leichter Niederschlag
                        if cid == 200 or cid == 210 or cid == 230 or cid == 300 or cid == 310 or cid == 500 or cid == 520 or cid == 600 or cid == 611 or cid == 612 or cid == 613 or cid == 615 or cid == 620:
                                rain_level = 1
                        # mittlerer Niederschlag
                        elif cid == 201 or cid == 211 or cid == 231 or cid == 301 or cid == 311 or cid == 313 or cid == 321 or cid == 501 or cid == 511 or cid == 521 or cid == 531 or cid == 601 or cid == 616 or cid == 621:
                                        rain_level = 2
                        # starker Niederschlag
                        elif cid == 202 or cid == 212 or cid == 232 or cid == 302 or cid == 312 or cid == 314 or cid == 502 or cid == 522 or cid == 602 or cid == 622:
                                        rain_level = 3
                        # extremer Niederschlag
                        elif cid == 503 or cid == 504:
                                        rain_level = 4

        return rain_level, condition_icon, cid, description


def openweathermap():
        data = {}
        r = requests.get(openweathermap_api_url)

        if r.status_code == 200:
#        current_data = r.json()
#        data['weather'] = current_data['main']
#        rain, rain_level = get_rain_level_from_weather(current_data['weather'])
#        data['weather']['rain'] = rain
#        data['weather']['rain_level'] = rain_level

                forecast = r.json()['list']
                data['forecast'] = []
                for f in forecast:
                        rain_level, condition_icon, condition_id, description = get_rain_level_from_weather(f['weather'])
                        data['forecast'].append({
                                "dt": f['dt'],
                                "dt_txt": f['dt_txt'],
                                "fields": {
                                        "temp": float(f['main']['temp']),
                                        "description": description,
                                        "icon": condition_icon,
                                        "weather_id": condition_id,
                                        "rain_level": int(rain_level),
                                        "pressure": float(float(f['main']['pressure']))
                                }
                        })
        return data

def persists(measurement, fields, time):
        i=1
        logging.info("{} {}".format(time, fields))
        json_body = [
                {
                        "measurement": measurement,
                        "time": time,
                        "fields": fields
                }
        ]
        influx_client.write_points(json_body)

def get_owmdata():
        try:
                out_info = openweathermap()
                for f in out_info['forecast']:
                        atime = str(f['dt']) + 'Z'
                        persists(measurement='forecast',
                                fields=f['fields'],
#                               time=atime)
                                time=datetime.datetime.utcfromtimestamp(f['dt']).isoformat())

        except Exception as err:
                logging.error(err)



influx_client = InfluxDBClient(host='localhost', port=8086)
influx_client.switch_database('owmweather')
influx_client.query('delete from forecast')

get_owmdata()


# nur die Tages-Icons
#current = influx_client.query('select icon from forecast where icon =~ /.d/;')
current = influx_client.query('select icon from forecast;')
icons = list(current.get_points())

ii = 1
for ic in icons:
        imagesourcepath = '/opt/weather-dashboard/grafana/images/'+ic['icon']+'.png'
        imagedestpath = '/var/www/html/f'+str(ii)+'.png'
        cmd = 'cp ' + imagesourcepath + ' ' + imagedestpath
        ii += 1
#       print(cmd)
        subprocess.run([cmd],shell=True)

i = 1
picrootpath = '/var/www/html/'
cmd = 'montage '
cmd1 = ''
while i < ii:
        cmd1 += picrootpath+'f'+str(i)+'.png '
        i += 1

cmd = cmd + cmd1 + ' -mode Concatenate -tile 40x1 -background none '+picrootpath+'forecast.png'
#print(cmd)
subprocess.run([cmd],shell=True)

influx_client.close()