The Zambretti Forecaster, an alternative weather forecast

If you want to try a different approach for a weather forecast than my simple weather forecast, based on the barometric pressure history, then you could use the so called Zambretti Forecaster. The Zambretti Forecaster is a weather forecasting tool in the form of a circular slide rule that was introduced by the company Negretti and Zambra in 1920. A more detailed description about the functionality of the Zambretti Forecaster and how to put it into code can be found here.

Below, you can find my Python implementation of the Zambretti Forecaster algorithm. It uses the current barometric pressure, the trend of the barometric pressure (over the last six hours) and the current month of the year as an input. The output of the Zambretti Forecaster is one of 26 different weather conditions. I mapped these weather conditions to a set of four forecast icons (sun, cloud, rain and thunderstorm), to make it more simple. The forecast icon is then shown together with an icon (arrow) that represents the current trend of the barometric pressure. Of course, you could also use more than these four icons, to make the forecast better if you want.

#!/usr/bin/python
 
import os
import shutil
import rrdtool
 
# Filtering the pressure change by using the average from different periods should give better, more stable results
# Read the pressure values for now/t=0, t=-0.5h, t=-1.0h, t=-1.5h, t=-2.0h, t=-2.5h, t=-3.0h, t=-3.5h, t=-4.0h, t=-4.5h, t=-5.0h, t=-5.5h and t=-6.0h
press0 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 600', '-s -600')
press1 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -1800', '-e -1800')
press2 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -3600', '-e -3600')
press3 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -5400', '-e -5400')
press4 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -7200', '-e -7200')
press5 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -9000', '-e -9000')
press6 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -10800', '-e -10800')
press7 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -12600', '-e -12600')
press8 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -14400', '-e -14400')
press9 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -16200', '-e -16200')
press10 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -18000', '-e -18000')
press11 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -19800', '-e -19800')
press12 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -21600', '-e -21600')

# Calculate the single differences and normalize them to a change in pressure over 1h
pressurerdiff1 = (press0[2][0][1] - press1[2][0][1])*2
pressurerdiff2 = (press0[2][0][1] - press2[2][0][1])
pressurerdiff3 = (press0[2][0][1] - press3[2][0][1])/1.5
pressurerdiff4 = (press0[2][0][1] - press4[2][0][1])/2
pressurerdiff5 = (press0[2][0][1] - press5[2][0][1])/2.5
pressurerdiff6 = (press0[2][0][1] - press6[2][0][1])/3
pressurerdiff7 = (press0[2][0][1] - press7[2][0][1])/3.5
pressurerdiff8 = (press0[2][0][1] - press8[2][0][1])/4
pressurerdiff9 = (press0[2][0][1] - press9[2][0][1])/4.5
pressurerdiff10 = (press0[2][0][1] - press10[2][0][1])/5
pressurerdiff11 = (press0[2][0][1] - press11[2][0][1])/5.5
pressurerdiff12 = (press0[2][0][1] - press12[2][0][1])/6

# Calculate the average of the differences
pressurerdiff = (pressurerdiff1 + pressurerdiff2 + pressurerdiff3 + pressurerdiff4 + pressurerdiff5 + pressurerdiff6 + pressurerdiff7 + pressurerdiff8 + pressurerdiff9 + pressurerdiff10 + pressurerdiff11 + pressurerdiff12)/12

# Get the current pressure
currentpress = press0[2][0][1]

# Calculate the trend
if pressurerdiff < -0.25:
    trend = -1
elif pressurerdiff >= -0.25 and pressurerdiff <= 0.25:
    trend = 0
elif pressurerdiff > 0.25:
    trend = 1

# Get the current month
today = datetime.date.today()
date = datetime.datetime.strptime(str(today), "%Y-%m-%d")
month = int(date.month)

# Use the Zambretti-algorithm to finally make the forecast
# --------------------------------------------------------
# Falling Conditions
# ------------------
if trend == -1:
    shutil.copyfile('/home/pi/pressure_info/DownRight.png', '/var/www/html/Arrow.png')
    zambretti = 0.0009746*currentpress*currentpress-2.1068*currentpress+1138.7019
    if month < 4 | month > 9:
        zambretti = zambretti + 1
    zambretti = int(round(zambretti))
    if zambretti == 1:
        forecast = 'Settled Fine'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 2:
        forecast = 'Fine Weather'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 3:
        forecast = 'Fine Becoming Less Settled'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 4:
        forecast = 'Fairly Fine Showers Later'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 5:
        forecast = 'Showery Becoming unsettled'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 6:
        forecast = 'Unsettled, Rain later'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 7:
        forecast = 'Rain at times, worse later'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 8:
        forecast = 'Rain at times, becoming very unsettled'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 9:
        forecast = 'Very Unsettled, Rain'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
# Steady Conditions
# -----------------
elif trend == 0:
    shutil.copyfile('/home/pi/pressure_info/Right.png', '/var/www/html/Arrow.png')
    zambretti = 138.24-0.133*currentpress
    zambretti = int(round(zambretti))
    if zambretti == 1:
        forecast = 'Settled Fine'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 2:
        forecast = 'Fine Weather'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 3:
        forecast = 'Fine, Possibly showers'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 4:
        forecast = 'Fairly Fine, Showers likely'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 5:
        forecast = 'Showery Bright Intervals'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 6:
        forecast = 'Changeable some rain'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 7:
        forecast = 'Unsettled, rain at times'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 8:
        forecast = 'Rain at Frequent Intervals'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 9:
        forecast = 'Very Unsettled, Rain'
        shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    elif zambretti == 10:
        forecast = 'Stormy, much rain'
        shutil.copyfile('/home/pi/pressure_info/Storm.png', '/var/www/html/Forecast.png')
# Rising Conditions
# -----------------
elif trend == 1:
    shutil.copyfile('/home/pi/pressure_info/UpRight.png', '/var/www/html/Arrow.png')
    zambretti = 142.57-0.1376*currentpress
    if month < 4 | month > 9:
        zambretti = zambretti + 1
    zambretti = int(round(zambretti))
    if zambretti == 1:
        forecast = 'Settled Fine'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 2:
        forecast = 'Fine Weather'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 3:
        forecast = 'Becoming Fine'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 4:
        forecast = 'Fairly Fine, Improving'
        shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    elif zambretti == 5:
        forecast = 'Fairly Fine, Possibly showers, early'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 6:
        forecast = 'Showery Early, Improving'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 7:
        forecast = 'Changeable, Improving'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 8:
        forecast = 'Rather Unsettled Clearing Later'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 9:
        forecast = 'Unsettled, Probably Improving'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 10:
        forecast = 'Unsettled, short fine Intervals'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 11:
        forecast = 'Very Unsettled, Finer at times'
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
    elif zambretti == 12:
        forecast = 'Stormy, possibly improving'
        shutil.copyfile('/home/pi/pressure_info/Storm.png', '/var/www/html/Forecast.png')
    elif zambretti == 13:
        shutil.copyfile('/home/pi/pressure_info/Storm.png', '/var/www/html/Forecast.png')
        forecast = 'Stormy, much rain'

 

Moving from Weather Underground to OpenWeatherMap

Since Weather Underground has announced the End of Service for their API by the end of 2018, I stopped uploading data from my weather station to Weather Underground and started to look for an alternative API in order to still being able to display the current weather conditions on my Pimoroni Scroll pHAT HD display and the current air pressure on my GUI for rtl_433, along with the received temperature and humidity.

With OpenWeatherMap, I found a good substitute for that. They also provide an API that can be used for free for a limited amount of calls. For the average hobbyist, the limitations of the API does not matter. Like for Weather Underground in the past, you also have to get an API-Key, before you will be able to use the API.

In the following picture, you can see a small Windows program (similar to my rtl_433-GUI) that I wrote for learning about the OpenWeatherMap-API. It displays the name of the city where the data is coming from, an icon that represents the current weather conditions and the values for temperature, humidity and air pressure. A complete description of the weather conditions and the weather icons used by OpenweatherMap can be found here.

Wetterstation_OWM

I will leave all the Weather Underground related articles here unchanged for reference, but additionally, I will provide a working example on how to use the Openweathermap-API in this article, so that you will be able to adapt the code that uses the Weather Underground-API, to now use the OpenWeatherMap-API.

Using the OpenWeatherMap-API is straightforward and very similar to using the one from Weather Underground. Anyway, there is one thing that I’ve noticed when testing the API. It sometimes delivers the air pressure using one of two different ways. I pay attention to this in my example code so that it should work, either if the API delivers the normalized pressure at sea level via ‘pressure’, or via ‘sea_level’. Below, you can see the code that I use within the program that I’ve shown above. Only thing you have to do before you can use the code, is to replace YOURCITYID with the id of the city you want to get the data for and YOURAPPID with your personal API-Key. How to determine the City-Id is described here.

import requests
import json

try:
    OWM_str = 'http://api.openweathermap.org/data/2.5/weather?id=YOURCITYID&appid=YOURAPPID'
    response = requests.get(OWM_str)
    x = response.json() 

    if x['cod'] != '404':
        y = x['main']
        temp = y['temp']
        temp_float = float(temp)
        temp_float = temp_float-273.15
        temp_float = round(temp_float, 1)
        temp = str(temp_float)
        humi = y['humidity']
        temp_humi = float(humi)
        temp_humi = round(temp_humi, 1)
        humi = str(temp_humi)
        if 'sea_level' not in y:
            press = y['pressure']
            press_float = float(press)
            press_float = round(press_float, 1)
            press = str(press_float)
        else:
            press = y['sea_level']
            press_float = float(press)
            press_float = round(press_float, 1)
            press = str(press_float)
        z = x['weather']
        icon = z[0]['icon']
        icon = icon + '.png'
        city = x['name']
    else:
        temp = "--.-"
        humi = "--.-"
        press = '---.-'
        icon = '-'
        city = '-'
except:
    temp = "--.-"
    humi = "--.-"
    press = "---.-"
    icon = '-'
    city = '-'

Getting the stats out of Pi-Hole

If you want to be informed about your Pi-Hole statistics, but don’t want to visit the Pi-Hole dashboard all the time, then there is also an alternative way to get the statistics out of Pi-Hole, and use the numbers to present it in any other way that meets your demands. This could be e.g. showing it on a display (like the Scroll pHAT HD), uploading it to ThingSpeak, or whatever else you can think of.

Pi-Hole already provides you the opportunity to make a request for getting some statistic data right out of the box.  You just have to put the following line in the address bar of your browser and replace IPOFYOURPIRUNNINGPIHOLE with the IP address of your Raspberry Pi, on which Pi-Hole is running, and you will get a list with various numbers and the description for them.

http://IPOFYOURPIRUNNINGPIHOLE/admin/api.php

The result is given to you in the JSON data format and can easily be processed with Python, just like shown in previous scripts here in the blog before.

PiHoleStats

The following small Python script gets the data, extracts some interesting information,  formats it, and then outputs the final text on the command line.

#!/usr/bin/env python

import json
import urllib2

try:
    f = urllib2.urlopen('http://IPOFYOURPIRUNNINGPIHOLE/admin/api.php')
    json_string = f.read()
    parsed_json = json.loads(json_string)
    queries = parsed_json['dns_queries_today']
    adsblocked = parsed_json['ads_blocked_today']
    clients = parsed_json['unique_clients']
    f.close()
except:
    queries = '-'
    adsblocked = '-'
    clients = '-'

pihole = 'DNS-Queries: ' + str(queries) + ' - ' + 'Ads blocked: ' + str(adsblocked) + ' - ' + 'Devices: ' + str(clients)
print pihole

 

Raspberry Pi Zero W + Pimoroni Scroll pHAT HD = IoT Display

For a long time already, I always wanted to have some kind of display that permanently shows me different information. – Information that is useful for me personally. Recently there are a lot of new display technologies, like OLED and electronic paper display, but also conventional multi line LCDs became cheaper and cheaper during the last years. But most of these displays are meant to be read from a short distance, and my idea of such an information display was different. I wanted it to be:

Readable from all over the apartment

Convenient to read in all light conditions

While reading the latest news about the Raspberry Pi, I some when found out that Pimoroni has released a LED display for the Raspberry Pi, called Scroll pHAT HD. That display looked like it was exactly what I was waiting for. Some may say that scrolling LED displays are a bit old fashioned, but in my opinion they are not. When I’m sitting at the dinning table in the morning and having breakfast, reading the display is fun and it reassures  me that the Scroll pHAT HD is just the right display for my purpose.

So, what to finally show on such a display? Actually, there’s not too much to think about this in my case, a small and compact summary of my weather station website, of course! 🙂 I came to the conclusion that the following information would be most useful and interesting for me:

Current weekday, date and time

Indoor temperature and indoor air quality

Outdoor weather data and conditions

Local gas price

Weekday, date and time are easy to get in Python, indoor temperature comes from my FRITZ!DECT 200, air quality from my air quality sensor via ThingSpeak, outdoor weather data and conditions from Weather Underground and the local gas price is provided by Tankerkönig. Here we go…

Because of it’s compact size and the built in WI-FI, I connected the Scroll pHAT HD to a Raspberry Pi Zero W. In front of the display, I’ve placed a custom made diffusor foil for better readability. The foil was the front of a binder that I found in an office supply store, simple but very efficient. The Raspberry Pi Zero W has a small plastic case and sits on an original Raspberry Pi power supply, directly connected to a power outlet (you may also want to see the 3D printed case from this guy, which holds the Raspberry Pi Zero W and the Scroll pHAT HD within one enclosure). In the video below, you can see the finished result. The script that is used runs every ten minutes and displays the information six times in a row. Except weekday, date and time, all the other information is only gathered once (at the beginning of the script) and it does not change during the ten minutes time period.

The script that I use is based on the advanced scrolling example from Pimoroni. This is already a good starting point for your own scripts. I especially like that you can put connected information into a single line, which then, after the horizontal scrolling of the line has finished, scrolls vertically to the next line of connected information.

But like usual, quite a bit of extra work on the script was required, until it finally worked in the way, it was supposed to. First of all, it is not possible to use mutated vowels (or other Unicode characters) within Python 2.7 without taking care of the encoding. There are a lot of discussions about that topic on the Internet and I ended up with doing something that actually should no be done, because I was just not able to find a better and cleaner solution that actually works. So, I’m aware that there still could be a bit room for improvement here.

Also, the font file that is originally provided by Pimoroni lacks of some characters that I want to display, that means that I had to modify it to show my own, custom characters/symbols, like the smileys, the superscript nine and the Euro sign. Actually this is not very hard to do. You just have to find an unused character in the font that you want to turn into a custom one and then edit the corresponding entry in the font file font5x7.py. If you are using Notepad++ and select ‘0xff’ somewhere, it will automatically highlight all other occurrences of ‘0xff’. This will help you to clearly see and modify the character.

Euro

For using the additional characters from the font file in the text you want to show on the display, you have to use unichr() in the code.

The major problem when adapting the script  for my purpose was actually, that the length of the text to display is not always the same. Since the weather conditions can be different, the total amount of the characters (responsively the total amount of the horizontal pixel) can vary. This means that the scrolling speed has to be a bit faster for longer texts and a bit slower for shorter texts, so that it can be displayed six times within the ten minutes time period, without having different waiting times at the end.

One should think that the scrolling speed and the total amount of the horizontal pixel are linearly dependent, but after a lot of trial and error, I have learned that this is not the case. Finally, I ended up with making some real world tests with different texts and deviating an equation that describes the connection between the total amount of he horizontal pixel and the necessary delay (responsively  the scrolling speed). All in all, it was a bit tricky, but I think the result is quite satisfying.

Graph

Below, you can see the entire script for the information display. It includes gathering all the information and displaying it in the way that is shown in the video above. The script also includes translation tables for the weather conditions and for the weekday that you can remove, if English is your first language. Of course, you can also use the tables to make your own translations into a different language than German.

Please note, that for reading the indoor temperature, the Bash script from the article ‘Reading the temperature from FRITZ!DECT devices’ is necessary. Additionally, YOURCHANNELID, YOURTHINGSPEAKAPIKEY, YOURAPIKEY, YOURFIELDNAME, YOURAPIKEYHERE, YOURSTATIONIDHERE, and DESIREDSTATIONID have to be replaced with your personal information.

#!/usr/bin/env python
# encoding: utf-8
import sys
import os
import shutil
import time
import urllib2
import json
import unicodedata
import scrollphathd
from scrollphathd.fonts import font5x7


reload(sys)
sys.setdefaultencoding("utf-8")


# Get the temperature from the FRITZ!DECT 200
try:
    process = os.popen('sudo ./fritztemp.sh')
    fritztemp = process.read()
    process.close()

    fritztemp = fritztemp.replace(',', '.')
    fritztemp = fritztemp.replace('\n', '')
except:
    fritztemp = '-'

indoortemp = 'Innentemperatur: ' + fritztemp + ' ' + unichr(20) + 'C'


# Get the air quality from ThingSpeak
try:
    f = urllib2.urlopen('http://api.thingspeak.com/channels/YOURCHANNELID/feeds/last.json?api_key=YOURTHINGSPEAKAPIKEY')
    json_string = f.read()
    parsed_json = json.loads(json_string)
    ppm = parsed_json['YOURFIELDNAME']
    f.close()

    ppm_float = float(ppm)

    smiley = ''
    if (ppm_float < 1000):
       smiley = ' ' + unichr(40) + unichr(2) + unichr(41)
    elif (ppm_float >= 1000 and ppm_float < 1500):
       smiley = ' ' + unichr(40) + unichr(3) + unichr(41)
    elif (ppm_float >= 1500):
       smiley = ' ' + unichr(40) + unichr(4) + unichr(41)
except:
    ppm = '-'
    smiley = ''

airquality = 'Luftqualit' + unichr(145) + 't: ' + ppm + ' ppm' + smiley


# Get the weather data from Weather Underground
try:
    f = urllib2.urlopen('http://api.wunderground.com/api/YOURAPIKEYHERE/geolookup/conditions/q/pws:YOURSTATIONIDHERE.json')
    json_string = f.read()
    parsed_json = json.loads(json_string)
    temp_c = parsed_json['current_observation']['temp_c']
    relative_humidity = parsed_json['current_observation']['relative_humidity']
    relative_humidity = relative_humidity.replace('%', '')
    pressure_in = parsed_json['current_observation']['pressure_in']
    pressure_trend = parsed_json['current_observation']['pressure_trend']
    conditions = parsed_json['current_observation']['weather']
    f.close()

    temp_str = '%.1f' % temp_c
    press_float = float(pressure_in)
    press_float = press_float*33.8637526
    press_str =  '%.1f' % press_float

    trend = ''
    if pressure_trend == '0':
      trend = ' ' + unichr(28)
    elif pressure_trend == '+':
      trend = ' ' + unichr(30)
    elif pressure_trend == '-':
      trend = ' ' + unichr(31)

    conditions = conditions.replace('Light ', '')
    conditions = conditions.replace('Heavy ', '')

    # Make a German translation
    if conditions == 'Drizzle':
      conditions = conditions.replace('Drizzle', 'Nieselregen')
    elif conditions == 'Rain':
      conditions = conditions.replace('Rain', 'Regen')
    elif conditions == 'Snow':
      conditions = conditions.replace('Snow', 'Schnee')
    elif conditions == 'Snow Grains':
      conditions = conditions.replace('Snow Grains', 'Griesel')
    elif conditions == 'Ice Crystals':
      conditions = conditions.replace('Ice Crystals', 'Eisgl' + unichr(145) + 'tte')
    elif conditions == 'Ice Pellets':
      conditions = conditions.replace('Ice Pellets', 'Graupel')
    elif conditions == 'Hail':
      conditions = conditions.replace('Hail', 'Hagel')
    elif conditions == 'Mist':
      conditions = conditions.replace('Mist', 'Feuchter Dunst')
    elif conditions == 'Fog':
      conditions = conditions.replace('Fog', 'Nebel')
    elif conditions == 'Fog Patches':
      conditions = conditions.replace('Fog Patches', 'Nebelschwaden')
    elif conditions == 'Smoke':
      conditions = conditions.replace('Smoke', 'Rauch')
    elif conditions == 'Volcanic Ash':
      conditions = conditions.replace('Volcanic Ash', 'Vulkanasche')
    elif conditions == 'Widespread Dust':
      conditions = conditions.replace('Widespread Dust', 'Verbreitet staubig')
    elif conditions == 'Sand':
      conditions = conditions.replace('Sand', 'Sandig')
    elif conditions == 'Haze':
      conditions = conditions.replace('Haze', 'Dunst')
    elif conditions == 'Spray':
      conditions = conditions.replace('Spray', 'Spr' + unichr(177) + 'hregen')
    elif conditions == 'Dust Whirls':
      conditions = conditions.replace('Dust Whirls', 'Wirbelwind')
    elif conditions == 'Sandstorm':
      conditions = conditions.replace('Sandstorm', 'Sandsturm')
    elif conditions == 'Low Drifting Snow':
      conditions = conditions.replace('Low Drifting Snow', 'Schneeverwehungen')
    elif conditions == 'Low Drifting Widespread Dust':
      conditions = conditions.replace('Low Drifting Widespread Dust', 'Staubverwehungen')
    elif conditions == 'Low Drifting Sand':
      conditions = conditions.replace('Low Drifting Sand', 'Sandverwehungen')
    elif conditions == 'Blowing Snow':
      conditions = conditions.replace('Blowing Snow', 'Schneetreiben')
    elif conditions == 'Blowing Widespread Dust':
      conditions = conditions.replace('Blowing Widespread Dust', 'Staubtreiben')
    elif conditions == 'Blowing Sand':
      conditions = conditions.replace('Blowing Sand', 'Sandtreiben')
    elif conditions == 'Rain Mist':
      conditions = conditions.replace('Rain Mist', 'Spr' + unichr(177) + 'hregen')
    elif conditions == 'Rain Showers':
      conditions = conditions.replace('Rain Showers', 'Regenschauer')
    elif conditions == 'Snow Showers':
      conditions = conditions.replace('Snow Showers', 'Schneeschauer')
    elif conditions == 'Snow Blowing Snow Mist':
      conditions = conditions.replace('Snow Blowing Snow Mist', 'Schneegest' + unichr(169) + 'ber')
    elif conditions == 'Ice Pellet Showers':
      conditions = conditions.replace('Ice Pellet Showers', 'Graupelschauer')
    elif conditions == 'Hail Showers':
      conditions = conditions.replace('Hail Showers', 'Hagelschauer')
    elif conditions == 'Small Hail Showers':
      conditions = conditions.replace('Small Hail Showers', 'Kleink' + unichr(169) + 'rniger Hagelschauer')
    elif conditions == 'Thunderstorm':
      conditions = conditions.replace('Thunderstorm', 'Gewitter')
    elif conditions == 'Thunderstorms and Rain':
      conditions = conditions.replace('Thunderstorms and Rain', 'Gewitter und Regen')
    elif conditions == 'Thunderstorms and Snow':
      conditions = conditions.replace('Thunderstorms and Snow', 'Gewitter und Schnee')
    elif conditions == 'Thunderstorms and Ice Pellets':
      conditions = conditions.replace('Thunderstorms and Ice Pellets', 'Gewitter und Graupel')
    elif conditions == 'Thunderstorms with Hail':
      conditions = conditions.replace('Thunderstorms with Hail', 'Gewitter mit Hagel')
    elif conditions == 'Thunderstorms with Small Hail':
      conditions = conditions.replace('Thunderstorms with Small Hail', 'Gewitter mit kleink' + unichr(169) + 'rnigem Hagel')
    elif conditions == 'Freezing Drizzle':
      conditions = conditions.replace('Freezing Drizzle', 'Ueberfrierender Nieselregen')
    elif conditions == 'Freezing Rain':
      conditions = conditions.replace('Freezing Rain', 'Ueberfrierender Regen')
    elif conditions == 'Freezing Fog':
      conditions = conditions.replace('Freezing Fog', 'Ueberfrierender Nebel')
    elif conditions == 'Patches of Fog':
      conditions = conditions.replace('Patches of Fog', 'Nebelfelder')
    elif conditions == 'Shallow Fog':
      conditions = conditions.replace('Shallow Fog', 'Bodennebel')
    elif conditions == 'Partial Fog':
      conditions = conditions.replace('Partial Fog ', 'Teilweise neblig')
    elif conditions == 'Overcast':
      conditions = conditions.replace('Overcast', 'Bedeckt')
    elif conditions == 'Clear':
      conditions = conditions.replace('Clear', 'Klar')
    elif conditions == 'Partly Cloudy':
      conditions = conditions.replace('Partly Cloudy', 'Teilweise bew' + unichr(169) + 'lkt')
    elif conditions == 'Mostly Cloudy':
      conditions = conditions.replace('Mostly Cloudy', 'Meist bew' + unichr(169) + 'lkt')
    elif conditions == 'Cloudy':
      conditions = conditions.replace('Cloudy', 'Bew' + unichr(169) + 'lkt')
    elif conditions == 'Scattered Clouds':
      conditions = conditions.replace('Scattered Clouds', 'Aufgelockerte Bew' + unichr(169) + 'lkung')
    elif conditions == 'Small Hail':
      conditions = conditions.replace('Small Hail', 'Graupel')
    elif conditions == 'Squalls':
      conditions = conditions.replace('Squalls', 'Sturmb' + unichr(169) + 'en')
    elif conditions == 'Funnel Cloud':
      conditions = conditions.replace('Funnel Cloud', 'Trichterwolke')
    elif conditions == 'Unknown Precipitation':
      conditions = conditions.replace('Unknown Precipitation', 'Unbekannter Niederschlag')
    elif conditions == 'Unknown':
      conditions = conditions.replace('Unknown', 'Unbekannt')
except:
    temp_str = '-'
    relative_humidity = '-'
    press_str = '-'
    trend = ''
    conditions = '-'

weather = 'Aussentemperatur: ' + temp_str + ' ' + unichr(20) + 'C -' + ' Aussenluftfeuchtigkeit: ' + relative_humidity + ' % -' + ' Relativer Luftdruck: ' + press_str + ' hPa' + trend + ' -' + ' Wetterbedingungen: '  + conditions


# Get the gas price from Tankerkönig
try:
    f = urllib2.urlopen('https://creativecommons.tankerkoenig.de/json/detail.php?id=DESIREDSTATIONID&apikey=YOURAPIKEY')
    json_string = f.read()
    parsed_json = json.loads(json_string)
    gas = parsed_json['station']['e5']
    f.close()

    gas_str = '%.2f' % gas
except:
    gas_str = '-'

if gas_str == '-':
    gasprice = 'Benzinpreis Super E5: ' + gas_str + ' ' + unichr(91)
else:
    gasprice = 'Benzinpreis Super E5: ' + gas_str + unichr(36) + ' ' + unichr(91)


space = '    '
hyphen = ' - '
indoor = indoortemp + hyphen + airquality + space
weather = weather + space
gasprice = gasprice + space


# Show the thext on the display
for x in range(0, 6):
    try:
        # Get the weekday, date and time
        datetime = time.strftime('%A, %d.%m.%Y - %H:%M Uhr')
        # Make a German translation
        datetime = datetime.replace('Monday', 'Montag')
        datetime = datetime.replace('Tuesday', 'Dienstag')
        datetime = datetime.replace('Wednesday', 'Mittwoch')
        datetime = datetime.replace('Thursday', 'Donnerstag')
        datetime = datetime.replace('Friday', 'Freitag')
        datetime = datetime.replace('Saturday', 'Samstag')
        datetime = datetime.replace('Sunday', 'Sonntag')

        datetime = datetime + space

        scrollphathd.clear()
        scrollphathd.set_brightness(0.2)

        lines = [datetime,
                 indoor,
                 weather,
                 gasprice]

        line_height = scrollphathd.DISPLAY_HEIGHT + 2
        offset_left = 0

        lengths = [0] * len(lines)
        for line, text in enumerate(lines):
            lengths[line] = scrollphathd.write_string(text, x=offset_left, y=line_height * line, font=euro5x7)
            offset_left += lengths[line]

        scrollphathd.set_pixel(offset_left - 1, (len(lines) * line_height) - 1, 0)

        period = 96
        length = lengths[0] + lengths[1] + lengths[2] + lengths[3]
        # Factor bigger  -> Delay shorter -> Text runs faster
        # Factor smaller -> Delay longer  -> Text runs slower
        delay = period/(length*float(2.8))
        # The relationship between the amount of horizontal pixels and the scroll time is not linearly dependent 
        # Therefore, do a correction based on meassurements to adapt it to the conditions observed in reality
        delay = ((-0.0013889*length)+2.83472)*delay

        scrollphathd.scroll_to(0, 0)
        scrollphathd.show()

        # Scroll the text
        for current_line, line_length in enumerate(lengths):
            # Delay a slightly longer time at the start of each line
            time.sleep(delay*10)

            # Scroll to the end of the current line
            for x in range(line_length):
                scrollphathd.scroll(1, 0)
                scrollphathd.show()
                time.sleep(delay)

            if current_line != len(lines) - 1:
                # Progress to the next line by scrolling upwards
                for y in range(line_height):
                    scrollphathd.scroll(0, 1)
                    scrollphathd.show()
                    time.sleep(delay)
    except:
        scrollphathd.clear()
        sys.exit(-1)


scrollphathd.clear()

A simple weather forecast, based on the barometric pressure history

Everybody knows the little weather stations that uses different symbols to show you a rough weather forecast, based on the change in the barometric pressure during the last hours.

Since I am also measuring the barometric pressure and having all the values from the past already in a database, I thought it would be nice to also make a small and independent forecast that is just based on the barometric pressure history.

There are not so much information available that shows you the details of that kind of algorithm. Only thing to find are general statements like rising pressure means good/better weather and falling weather means bad/worse weather. So, I ended up to implement my own little algorithm that should give similar, or even better results than a small weather station will give you.

Most of the small weather stations are using three basic symbols, rain, sun/cloud and sun and also an arrow up and an arrow down for the tendency. In case of a quickly falling pressure, the arrow down will blink as a storm warning. Since I don’t want to have something blinking on the website of my weather station, I introduced two more symbols, one for quickly falling pressure, and also one for quickly raising pressure. Additionally, I introduced three more arrows, to make the tendency a bit more detailed. The following table shows you the possible combination of the symbols and the meaning of it.

Unstable Up Quickly rising, very unstable weather condition

Sun UpRight Slowly rising, good weather condition, tendency rising

Sun Right Good weather condition, tendency stable

SunCloud UpRight Condition change is possible, tendency rising

SunCloud Right Condition change is possible, tendency steady/unclear

SunCloud DownRight Condition change is possible, tendency falling

Rain Right Rainy weather condition, tendency stable

Rain DownRight Slowly falling, rainy weather condition, tendency falling

Storm Down Quickly falling, thunderstorm is highly possible

For making the forecast, I consider the change of the barometric pressure during the last six hours. To get better, more stable results, I implemented a filter by calculating the average of different periods.

This is what the weather forecast looks like in the status bar of the website of my weather station, together with the air quality, the current season, sunset and sunrise times and the current moon phase.

Benningen

The Python script for the weather forecast is shown below. The pressure values from the past are read from the RRDtool database and afterwards are used to calculate the value for the average pressure change during the last six hours. Afterwards a decision regarding the forecast is made and the two icons (or just one, in case of stable conditions) are copied to the directory of the web server. I was playing around a bit with the rates for falling and rising and settled with absolute values of 0.25, 0.42 and 0.75. These values are giving me good results here in Germany. If you live in another part in the world, you may increase or decrease the values to get satisfying results.

#!/usr/bin/python
 
import os
import shutil
import rrdtool
 
# Filtering the pressure change by using the average from different periods should give better, more stable results
# Read the pressure values for now/t=0, t=-0.5h, t=-1.0h, t=-1.5h, t=-2.0h, t=-2.5h, t=-3.0h, t=-3.5h, t=-4.0h, t=-4.5h, t=-5.0h, t=-5.5h and t=-6.0h
press0 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 600', '-s -600')
press1 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -1800', '-e -1800')
press2 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -3600', '-e -3600')
press3 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -5400', '-e -5400')
press4 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -7200', '-e -7200')
press5 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -9000', '-e -9000')
press6 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -10800', '-e -10800')
press7 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -12600', '-e -12600')
press8 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -14400', '-e -14400')
press9 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -16200', '-e -16200')
press10 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -18000', '-e -18000')
press11 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -19800', '-e -19800')
press12 = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 60', '-s -21600', '-e -21600')
 
# Calculate the single differences and normalize them to a change in pressure over 1h
pressurerdiff1 = (press0[2][0][1] - press1[2][0][1])*2
pressurerdiff2 = (press0[2][0][1] - press2[2][0][1])
pressurerdiff3 = (press0[2][0][1] - press3[2][0][1])/1.5
pressurerdiff4 = (press0[2][0][1] - press4[2][0][1])/2
pressurerdiff5 = (press0[2][0][1] - press5[2][0][1])/2.5
pressurerdiff6 = (press0[2][0][1] - press6[2][0][1])/3
pressurerdiff7 = (press0[2][0][1] - press7[2][0][1])/3.5
pressurerdiff8 = (press0[2][0][1] - press8[2][0][1])/4
pressurerdiff9 = (press0[2][0][1] - press9[2][0][1])/4.5
pressurerdiff10 = (press0[2][0][1] - press10[2][0][1])/5
pressurerdiff11 = (press0[2][0][1] - press11[2][0][1])/5.5
pressurerdiff12 = (press0[2][0][1] - press12[2][0][1])/6
 
# Calculate the average of the differences
pressurerdiff = (pressurerdiff1 + pressurerdiff2 + pressurerdiff3 + pressurerdiff4 + pressurerdiff5 + pressurerdiff6 + pressurerdiff7 + pressurerdiff8 + pressurerdiff9 + pressurerdiff10 + pressurerdiff11 + pressurerdiff12)/12
 
# Get the current pressure
currentpress = press0[2][0][1]
 
# Use the calculated pressure difference to finally make the forecast
# -------------------------------------------------------------------
# Rising Conditions
# -----------------
# Quickly rising, very unstable weather condition
if pressurerdiff > 0.75:
    shutil.copyfile('/home/pi/pressure_info/Unstable.png', '/var/www/html/Forecast.png')
    shutil.copyfile('/home/pi/pressure_info/Up.png', '/var/www/html/Arrow.png')
# Slowly rising, good weather condition, tendency rising
elif pressurerdiff > 0.42:
    shutil.copyfile('/home/pi/pressure_info/Sun.png', '/var/www/html/Forecast.png')
    shutil.copyfile('/home/pi/pressure_info/UpRight.png', '/var/www/html/Arrow.png')
# Change in weather condition is possible, tendency rising
elif pressurerdiff > 0.25:
    shutil.copyfile('/home/pi/pressure_info/UpRight.png', '/var/www/html/Arrow.png')
    if (currentpress >= 1006 and currentpress <= 1020) or currentpress < 1006:
        shutil.copyfile('/home/pi/pressure_info/SunCloud.png', '/var/www/html/Forecast.png')
# Falling Conditions
# ------------------
# Quickly falling, thunderstorm is highly possible
elif pressurerdiff < -0.75:
    shutil.copyfile('/home/pi/pressure_info/Storm.png', '/var/www/html/Forecast.png')
    shutil.copyfile('/home/pi/pressure_info/Down.png', '/var/www/html/Arrow.png')
# Slowly falling, rainy weather condition, tendency falling
elif pressurerdiff < -0.42:
    shutil.copyfile('/home/pi/pressure_info/Rain.png', '/var/www/html/Forecast.png')
    shutil.copyfile('/home/pi/pressure_info/DownRight.png', '/var/www/html/Arrow.png')
# Condition change is possible, tendency falling
elif pressurerdiff < -0.25:
    shutil.copyfile(’/home/pi/pressure _info/DownRight.png’, ’/var/www/html/Arrow.png’)
    if (currentpress >= 1006 and currentpress <= 1020) or currentpress > 1020:
        shutil.copyfile(’/home/pi/pressure _info/SunCloud.png’, ’/var/www/html/Forecast.png’)
# Steady Conditions
# -----------------
# Condition is stable, don't change the weather symbol (sun, rain or sun/cloud), just change the arrow
elif pressurerdiff <= 0.25 and pressurerdiff >= -0.25:
    shutil.copyfile('/home/pi/pressure_info/Right.png', '/var/www/html/Arrow.png')

Determining the indoor air quality with your Raspberry Pi

If you are considering to measure the air quality inside your house or apartment and start reading some online resources about that topic, for sure you will find a lot of information about the famous MQ-sensors. Especially the MQ135, which is designed to determine the air quality, would be a perfect sensor for that task. But the big downside of these sensors is that they deliver an analog signal, so that an additional A/D converter or a micro controller, like e.g. the Arduino, is necessary to convert the analog signal to a digital signal that then can be used by the Raspberry Pi. For sure, this is not such a big deal, but I was looking for a more ‘consolidated’ solution.

Finally, I found an air quality sensor, manufactured by Rehau, that can be connected directly to an USB port, already containing the sensor and a micro controller in the case of a small USB stick. This sensor does not just simply measures the amount of carbon dioxide and takes it as an indicator for the air quality, like e.g. the Netatmo weather station does, it is sensing volatile organic compounds that are produced by cooking, breathing, sweating and so on. Together with the amount of carbon monoxide, the sensor calculates a value for the air quality in ppm. Values up to 1000ppm are standing for good air quality, values from 1000ppm to 1500ppm representing mediocre air quality, and if the value exceeds 1500ppm, the air quality is considered bad. You can also see the current condition of the air directly at the stick, since it is shown by using a green, a yellow and a red LED.

Important thing about this stick is, that you have to do a calibration before you are using it for measuring and recording the air quality. For that, you just have to plug the stick in, when it is surrounded by clean and fresh air, that’s basically all. Unfortunately, the stick does not keep the value from the calibration permanently. That means, every time it is powered up (or the Raspberry Pi is restarted), the calibration happens again. But there is a little trick, with which you can avoid a new calibration after a restart. The sensor comes together with a Windows software called ‘Airmonitor’. If you start this software, press and hold ‘Strg’ and then click on the logo three times, the so called ‘Expert Mode’ within the software will be activated. After that, you can go to ‘Support Tools’, ‘Edit Knobs’ and change the the value for ‘ui16StartupBits’ to ‘0’. For further reading about the Rehau Air Quality Sensor, I recommend the article here.

Fortunately,  a program for Linux already exists that can be used on the Raspberry Pi to read the values from the sensor. You can easily download the sources of the program an build an executable, by simply following the steps provided by the author of the program.

sudo apt-get install libusb-dev
sudo apt-get install build-essential
mkdir airsensor
cd airsensor
wget http://usb-sensors-linux.googlecode.com/svn/trunk/airsensor/airsensor.c
gcc -o airsensor airsensor.c -lusb

Like all the other data from my weather station, and also the system information from the Raspberry Pi, I also store the air quality values in a RRDtool database and use it for making a graph that shows the air quality values from the last 24 hours. In that graph, I also include the thresholds for good, mediocre and bad air quality, so that it is very easy to notice, in which condition the air was at a given point in time.

airquality

Additionally, I included a little smiley icon in the status bar of my weather station website, to indicate the current condition of the air in the same way, I did for showing the warning level of my lightning detector.

Happy     Air quality is good

Neutral     Air quality is mediocre

Sad     Air quality is bad

This is what the smiley indicator looks like in the status bar of the website of my weather station, together with the weather forecast, the current season, sunset and sunrise times and the current moon phase.

Benningen

I also upload the values for the air quality to ThingSpeak and use the IoT ThingSpeak Monitor Widget to monitor the air quality on my Smartphone. This little widget can also generate a notification, if the air quality exceeds a specified threshold, so that you are reminded to open the windows and get some fresh air into the room, if you want.

Telefon

This is the Python script that I use for reading the air quality, updating the icon that represents the current condition of the air, and uploading the value to ThingSpeak. If you also have an account at ThingSpeak, you can replace YOURTHINGSPEAKWRITEKEY with your own personal write key. Please note that reading the value from the sensor can take several seconds, so if you e.g. call the Python script from within a Bash script, it should be followed by a sleep of about 20 seconds.

#!/usr/bin/python

import sys
import os
import shutil
import rrdtool
import httplib, urllib
import time

writekey = 'YOURTHINGSPEAKWRITEKEY'

# Read the current airquality
process = os.popen('sudo ./airsensor -o')
str = process.read()
process.close()

if not str:
	sys.exit(1)

str = str.strip()
str = str.replace(',', '')
data = str.split(' ')

ppm = int(float(data[3]))

# Check the output and store it in the database, if it is ok
if ppm > 0:
	# Insert data into database
	update = 'N:' + data[3]
	rrdtool.update("%s/airquality.rrd" % (os.path.dirname(os.path.abspath(__file__))), update)
	if (ppm < 1000):
		shutil.copyfile('/home/pi/airsensor/Happy.png', '/var/www/html/Smiley.png')
	elif (ppm >= 1000 and ppm < 1500):
		shutil.copyfile('/home/pi/airsensor/Neutral.png', '/var/www/html/Smiley.png')
	elif (ppm >= 1500):
		shutil.copyfile('/home/pi/airsensor/Sad.png', '/var/www/html/Smiley.png')
	# Post to ThingSpeak
	try:
		params = urllib.urlencode({'field1': ppm, 'key':writekey })
		headers = {"Content-typZZe": "application/x-www-form-urlencoded","Accept": "text/plain"}
		conn = httplib.HTTPConnection("api.thingspeak.com:80")
		conn.request("POST", "/update", params, headers)
		response = conn.getresponse()
		data = response.read()
		conn.close()
	except:
		sys.exit(-1)

Connecting a lightning detector to your Pi that warns you by Email

There is a nice little electronic kit from Franzis available, where you can build a lightning detector that shows you roughly the distance of thunderstorms around you and that may approach your location. The kit itself is powered by two AA batteries, but can also be powered by the Raspberry Pi’s 3.3V supply. The lightning detector includes four LEDs, from which one is flashing every time a lightning is detected. The other LEDs three gives you a warning level (green, yellow and red) for detected thunderstorms in a distance of about 50km, 30km and 10km.

Gewitterwarner

Besides the LEDs, there are also the connections M1 and M2 which provides a binary output of the warning level (00, 01, 10 and 11). You can connect these outputs  directly to the GPIOs of the Raspberry Pi, since both of them use 3.3V as a HIGH level. By checking the output of the lightning detector periodically with a Raspberry Pi, you are able to show the current warning level on the website of your weather station. For that, I use the combination of two icons, a bar indicator and a smiley/exclamation mark.

Stufe0 Happy     No thunderstorm within detection range

Stufe1 Neutral     Thunderstorm about 50km away

Stufe2 Sad     Thunderstorm about 30km away

Stufe3 Attention     Thunderstorm about 10km away

This is what the warning level looks like in the status bar of the website of my weather station, together with the weather forecast, the current season, sunset and sunrise times and the current moon phase.

Hechingen

Of course, you are not looking at the website of your weather station all days long, so it would make sense to send an instant notification, if a certain warning level has been reached. I do this by sending an Email to my personal Email address which then will be received on my Smartphone immediately.

Since the script is running periodically, you have to remember, if there was an active warning, the last time the script was running, otherwise you would get an Email every time the script is running and conditions for a warning are met. Also, you would not be able to send an all-clear message, if  the warning level went down again to a safe level.

A simple file can be used as a token, to tell the script running next time that the script running before was setting the system into the warning state, so that Emails are only sent when the system enters the warning state (warning) and when it leaves the warning state (all-clear). The token file has to be created when the systems goes to the warning state and the warning Email is sent, and deleted when the system leaves the warning state and the all-clear Email is sent.

Below, you can find a Python script that reads the warning level from the lightning detector, copies the icons to the website directory accordingly to the warning level, and sends an Email (warning), if the highest warning level has been reached. An Email will also be send (all-clear), if the warning level drops below the second highest warning level. If you want to use the script, don’t forget to replace YOURSENDEREMAILADDRESS, YOURPASSWORD, YOURMAILSERVER and YOURRECEIVEREMAILADDRESS with your personal information.


#!/usr/bin/python

import RPi.GPIO as GPIO
import sys
import os
import shutil
import smtplib
import socket
import time
from email.mime.text import MIMEText

Sender = 'YOURSENDEREMAILADDRESS'
Password = 'YOURPASSWORD'
smtpserver = smtplib.SMTP('YOURMAILSERVER', 587)
smtpserver.ehlo()
smtpserver.starttls()
smtpserver.ehlo

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.IN) # GPIO_GEN2
GPIO.setup(22, GPIO.IN) # GPIO_GEN3

M1 = 0
M2 = 0

if GPIO.input(27) == GPIO.HIGH:
	M1 = 1
if GPIO.input(22) == GPIO.HIGH:
	M2 = 1

if (M1 == 0 and M2 == 0):
	shutil.copyfile('/home/pi/lightning/Level0.png', '/var/www/Lightning.png')
	shutil.copyfile('/home/pi/lightning/Happy.png', '/var/www/Smiley.png')
	if os.path.isfile("warningactive"):
		os.remove("warningactive")
		smtpserver.login(Sender, Password)
		Receiver = 'YOURRECEIVEREMAILADDRESS'
		Wert = "All-clear, thunderstorm is over!"
		msg = MIMEText(Wert)
		msg['Subject'] = 'All-clear! - %s' % time.strftime("%d.%m.%Y %H:%M:%S")
		msg['From'] = Sender
		msg['To'] = Receiver
		smtpserver.sendmail(Sender, [Receiver], msg.as_string())
		smtpserver.quit()
elif (M1 == 0 and M2 == 1):
	shutil.copyfile('/home/pi/lightning/Level1.png', '/var/www/Lightning.png')
	shutil.copyfile('/home/pi/lightning/Neutral.png', '/var/www/Smiley.png')
	if os.path.isfile("warningactive"):
		os.remove("warningactive")
		smtpserver.login(Sender, Password)
		Receiver = 'YOURRECEIVEREMAILADDRESS'
		Wert = "All-clear, thunderstorm is over!"
		msg = MIMEText(Wert)
		msg['Subject'] = 'All-clear! - %s' % time.strftime("%d.%m.%Y %H:%M:%S")
		msg['From'] = Sender
		msg['To'] = Receiver
		smtpserver.sendmail(Sender, [Receiver], msg.as_string())
		smtpserver.quit()
elif (M1 == 1 and M2 == 0):
	shutil.copyfile('/home/pi/lightning/Level2.png', '/var/www/Lightning.png')
	shutil.copyfile('/home/pi/lightning/Sad.png', '/var/www/Smiley.png')
	smtpserver.quit()
elif (M1 == 1 and M2 == 1):
	shutil.copyfile('/home/pi/lightning/Level3.png', '/var/www/Lightning.png')
	shutil.copyfile('/home/pi/lightning/Attention.png', '/var/www/Smiley.png')
	if (os.path.isfile("warningactive") == False):
		os.mknod("warningactive")
		smtpserver.login(Sender, Password)
		Receiver = 'YOURRECEIVEREMAILADDRESS'
		Wert = "Attention, thunderstorm is coming!"
		msg = MIMEText(Wert)
		msg['Subject'] = 'Warning! - %s' % time.strftime("%d.%m.%Y %H:%M:%S")
		msg['From'] = Sender
		msg['To'] = Receiver
		smtpserver.sendmail(Sender, [Receiver], msg.as_string())
		smtpserver.quit()

Visualize the changes in the German gas price

If you live in Germany and want to make your own statistics about the changes in the gas price, or maybe want to integrate the gas price in your private Home Automation system display, you can do that by using the API from the website Tankerkönig.

You can get an API key and the full description of the API here. Once you have your personal API key, you can get the ID of your most favorite gas station by making an API call with the coordinates of your location and the radius to search within.

https://creativecommons.tankerkoenig.de/json/list.php?lat=52.521&lng=13.438&rad=1.5&sort=dist&type=all&apikey=YOURAPIKEY

You can just copy the call to the address bar of your browser and confirm with enter. Immediately, you should get a list of the gas stations that surround you within the given radius.

StationList

If you have found your favorite station within the list, you can use it’s unique ID to make an API call only for that particular station. I do this periodically, store the values in a RRDtool database and make graphs that show the development of the gas price during the last 24 hours and during the last seven days.

benzinpreis

benzinpreis7d

Below, you can see the Python script listed for making the API call and storing the result in a RRDtool database.

#!/usr/bin/env python

import sys
import os
import rrdtool
import urllib2
import json

try:
    # Get the gasprice for your desired gas station
    f = urllib2.urlopen('https://creativecommons.tankerkoenig.de/json/detail.php?id=DESIREDSTATIONID&apikey=YOURAPIKEY')
    json_string = f.read()
    parsed_json = json.loads(json_string)
    benzinpreis = parsed_json['station']['e5']
    f.close()
except:
    sys.exit(-1)

# Insert data into the database
data = "N:%.3f" % (benzinpreis)
rrdtool.update("%s/benzinpreis.rrd" % (os.path.dirname(os.path.abspath(__file__))), data)

Uploading APRS/CWOP weather data

Radio Amateurs built up a network called Automatic Position Reporting System (APRS), where they show positions of stationary, mobile and portable radio stations on a map. The station reports are normally transmitted by radio transmitters, but they can also be transmitted over the internet. These reports can also contain additional data like weather reports or telemetry data. All details about APRS can be found in the APRS Protocol Reference.

Normally, it is only allowed for licensed radio amateurs to transmit APRS data that will show up on the APRS map. But there is also a public-private partnership called Citizen Weather Observer Program (CWOP), which stations are also shown on the APRS map.

If you sign up for CWOP, you will get a station ID and you will be able to upload your weather data, so that your weather station will show up on aprs.fi, together with your station ID. On the Raspberry Pi, it offers itself to us a Python script for uploading APRS/CWOP data. There are a few little pitfalls within the APRS protocol which I want to mention. First, United States customary units are used which can be a bit confusing, if you are not familiar with it.

The other thing is the format of the coordinates. Although it contains a dot, the values are supposed to be in degrees, minutes and seconds. If you keep this in mind, should should be able to use the script below to successfully transmit APRS/CWOP data over the internet. Only thing you have to do is to add your station ID, your location and your weather data.

If you want to use the script as a licensed radio amateur, you just have to change the name and the port of the server (to one of the APRS network ones) and to use your call sign instead of the station ID. And of course, you have to provide your APRS password (instead of -1 in the script).

#!/usr/bin/env python

import sys, os, time
import rrdtool
from datetime import datetime, timedelta
from socket import *

serverHost = 'cwop.aprs.net'
serverPort = 14580
address = 'YOURSTATIOIDHERE>APRS,TCPIP*:'
# Attention, format of the location is bit special. Although there is a dot, the values are in degrees, minutes and seconds!
position = 'DDMM.SSN/DDDMM.SSE_'

def send_packet():
	# Get the values from the database
	weather = rrdtool.fetch("./../weather_info/weather.rrd", 'AVERAGE', '-r 720', '-s -720')
	press = rrdtool.fetch("./../pressure_info/pressure.rrd", 'AVERAGE', '-r 600', '-s -600')
	# Attention, temperature in Fahrenheit!
	fahrenheit = 9.0/5.0 * weather[2][0][0] + 32
	humidity = weather[2][0][1]
	# Attention, barometric pressure in tenths of millibars/tenths of hPascal!
	pressure = press[2][0][1] * 10

	# If you have wind and rain data, get it here. Be aware that values are required in mph and in hundredths of an inch!
	wind_degrees = ...
	wind_mph = ...
	wind_gust_mph = ...
	precip_1hr_in = ...
	precip_today_in = ...

	# Prepare the data, which will be sent
	wx_data = make_aprs_wx(int(float(wind_degrees)),float(wind_mph),float(wind_gust_mph),fahrenheit,f_precip_1hr_in,None,(float(precip_today_in)*100),humidity,pressure)
	# Use UTC
	utc_datetime = datetime.now()
	# Create socket and connect to server
	sSock = socket(AF_INET, SOCK_STREAM)
	sSock.connect((serverHost, serverPort))
	# Log on
	sSock.send('user YOURSTATIOIDHERE pass -1 vers Python\n')
	# Send packet
	sSock.send(address + '@' + utc_datetime.strftime("%d%H%M") + 'z' + position + wx_data + 'Raspberry Pi\n')
	# Close socket, must be closed to avoid buffer overflow
	sSock.shutdown(0)
	sSock.close()

def make_aprs_wx(wind_dir=None, wind_speed=None, wind_gust=None, temperature=None, rain_last_hr=None, rain_last_24_hrs=None, rain_since_midnight=None, humidity=None, pressure=None):
	# Assemble the weather data of the APRS packet
	def str_or_dots(number, length):
		# If parameter is None, fill with dots, otherwise pad with zero
		if number is None:
			return '.'*length
		else:
			format_type = {
				'int': 'd',
				'float': '.0f',
			}[type(number).__name__]
			return ''.join(('%0',str(length),format_type)) % number
	return '%s/%sg%st%sr%sp%sP%sh%sb%s' % (
		str_or_dots(wind_dir, 3),
		str_or_dots(wind_speed, 3),
		str_or_dots(wind_gust, 3),
		str_or_dots(temperature, 3),
		str_or_dots(rain_last_hr, 3),
		str_or_dots(rain_last_24_hrs, 3),
		str_or_dots(rain_since_midnight, 3),
		str_or_dots(humidity, 2),
		str_or_dots(pressure, 5),
	)

try:
	send_packet()
except:
	sys.exit(-1)

Measuring barometric pressure with the BMP180 and determining ambient temperature by correlation

Weather stations normally are able to measure the barometric pressure and will give you a weather trend based on the trend of the barometric pressure during the last couple of hours.

To measure the barometric pressure with my Raspberry Pi, I use the Bosch BMP180 pressure sensor. A very good Python library and a tutorial for using the the Bosch BMP180 (which is software compatible to the older BMP085) together with the Raspberry Pi is provided by Adafruit.

Only thing you have to be aware of, if you are going to measure the barometric pressure is, that the sensor will give you absolute values, but since the pressure is also changing over altitude, and you want to be able to compare the values of different location, the barometric pressure normally is normalized to sea level.

To calculate the normalized sea level barometric pressure,  you have to know the altitude above sea level of the place, where you are measuring the barometric pressure. This calculation is included in the Python script at the end of this page. I use it to read and also store the barometric pressure values in a RRDtool database and create graphes, accordingly I do for outside temperature and humidity.

pressure

The BMP180 does not only measure the pressure, it also measures the temperature. First of all, I did not want to use the temperature values from the sensor and so I put it directly inside the case of the Raspberry Pi, so that there are no additional cables around the Raspberry Pi and that the sensor itself is protected from the dust.

But then, I thought it would be nice to have both inside temperatures, the one from the lower floor (from the FRITZ!Dect200) and the one from the upper floor in one diagram, but still, I wanted to keep the sensor inside the case, because of the reasons I’ve mentioned before.

Of course, the temperature readings from the BMP180 are much to high inside the case of the Raspberry Pi. But what also is true, is that the measured temperature is a correlation between the outside ambient temperature and the temperature of the CPU. And the later one, you can also measure easily.

So, you have two known and one unknown variables, only thing left to do now is to find an equation, so can use the known variables to calculate the value of the unknown variable. At least a reasonable approximation should be possible. Since there are different add-on boards available for the Raspberry Pi which also contain temperature sensors, I was convinced that someone should have faced the same situation already. Fortunately, this was the case and a similar project found a very good approximation.

ambient = temp - ((cpu_temp - temp)/FACTOR)

For finally using the approximation, you have to measure the ambient temperature with a additional thermometer, read the current CPU temperature and the temperature from the sensor, and then calculate the value of FACTOR. If you do this multiple times and calculate an average, you should get a quite good value that should provide a satisfying approximation. From my experience, the readings should be around +/- 0.5°C around the real temperature.

cputemp

insidetemp2

You can clearly see the correlation between the CPU temperature and the calculated ambient temperature. Unfortunately, the CPU temperature is quite noisy and this noise also appears on the ambient temperature. Of course, you could do a simple low pass filtering of the results, but then you have to measure the temperatures quite often to avoid a longer delay between the last reading and the present value. I decided to leave it without filtering and measure the temperatures only every five minutes.

Below, you can find my Python script, which includes the reading from the BMP180 (or the older BMP085), the normalizing of the pressure to sea level, the temperature correction and the writing to the database.

#!/usr/bin/python

import os
import rrdtool
import Adafruit_BMP.BMP085 as BMP085

sensor = BMP085.BMP085(mode=BMP085.BMP085_ULTRAHIGHRES)

try:
	# Read the current temperature
	temp = sensor.read_temperature()
	# Read the current barometric pressure level
	pressure = sensor.read_pressure()
except:
	sys.exit(-1)

# Calculate the ambient temperature
cpu_temp = os.popen('vcgencmd measure_temp').readline()
cpu_temp = cpu_temp.replace("temp=","")
cpu_temp = cpu_temp.replace("'C\n","")
cpu = float(cpu_temp)
ambient = temp-((cpu-temp)/1.425)

# Set the altitude of your current location in meter
altitude = 229
psea = pressure / pow(1.0 - altitude/44330.0, 5.255)
psea = psea/100.0

# insert data into round-robin-database
data = "N:%.2f:%.2f" % (ambient, psea)
rrdtool.update("%s/pressure.rrd" % (os.path.dirname(os.path.abspath(__file__))), data)