Damit alle Messwerte einer Wetterstation mit openHAB angezeigt werden können, müssen diese irgendwie maschinell lesbar sein. Die Geräte, die man im Handel kaufen kann, haben aber keine Schnittstelle, die sowas ermöglicht. Auch funktionieren die allermeisten Systeme mit einer Batterie, die man periodisch auswechseln muss. Daher meine Fühler auf dem Dach montiert werden sollen, kommt diese Lösung nicht in Frage.
Als unsere PVA montiert wurde, habe ich gleich eine Ethernet-Kabel von Dach zum Keller eingezogen. Dieses ist mit dem POE-Switch kombiniert auch eine Stromquelle für Geräte. Daher habe ich mir nun eine Wetterstation gebaut, welche via REST-API die Daten im Heimnetz zur Verfügung stellt und mit POE gespiesen wird. Als Basis dient ein Raspberry Pi Zero mit POE-Hat. Daran habe ich die nötigen Sensoren und Bauteile verbunden.
Zuerst mal alles auf einem Breadboard entworfen und getestet:

Alle Sensoren sind verbunden, somit kann der Code entwickelt werden.

Es sieht komplizierter aus als es ist. Im Prinzip ist es ganz einfach:

Die Messdaten kommen als JSON in der Antwort auf eine HTTP GET Anfrage. Diese kann ich ganz einfach im openHAB pollen, anzeigen und loggen.
// http://pizero/values
{
"updated": "2023-11-07, 14:25:08",
"air": {
"temperature": 11.04,
"humidity": 63.79,
"pressure": 951.82
},
"wind": {
"speed": 0.9,
"direction": 225
},
"rain": {
"actual": 43,
"hour": 0.0,
"day": 5.03,
"week": 11.73
}
}
Install packages
sudo apt update sudo apt upgrade sudo apt install python3-dev sudo apt install python3-pip sudo pip3 install jedi sudo pip3 install Flask sudo pip3 install adafruit-circuitpython-ahtx0 sudo pip3 install adafruit-circuitpython-bmp280 sudo raspi-config #interface options - activate IC2 #interface - spi - enable #reboot raspberry shutdown -r now # test run sudo python -m flask --app serv.py run --host=0.0.0.0 --port=80 # install as service sudo cp rest-api@pi.service /etc/systemd/system/ sudo systemctl --system daemon-reload sudo systemctl enable rest-api@pi sudo systemctl start rest-api@pi
rest-api@pi.service
[Unit] Description=Rest API Service for Raspberry Pi After=network.target [Service] Type=simple User=root ExecStart=/usr/bin/python -m flask --app /home/username/serv.py run --host=0.0.0.0 --port=80 [Install] WantedBy=multi-user.target
The python code serv.py
from flask import Flask
import adafruit_ahtx0, adafruit_bmp280
import json, math, time, board, pickle
import RPi.GPIO as GPIO
from gpiozero import MCP3008, Button
from datetime import datetime
from collections import deque
GPIO.setwarnings(False) # Ignore warning for now
GPIO.setmode(GPIO.BCM)
file_path = "bucket_tips"
######################## air
i2c = board.I2C()
sensor_aht = adafruit_ahtx0.AHTx0(i2c)
sensor_bmp = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)
def getAir():
return {
"temperature": round(sensor_aht.temperature, 2),
"humidity": round(sensor_aht.relative_humidity, 2),
"pressure": round(sensor_bmp.pressure, 2)
}
######################## wind
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) #wind speed
half_turns = deque([])
read_span_sec = 10
anenometer_radius_meters = 0.09
anemometer_factor = 1.18
half_turns_multiplier = math.pi * anenometer_radius_meters * anemometer_factor
def spin(channel):
global half_turns
now = datetime.timestamp(datetime.now())
half_turns.append(now)
#print(f"anenometer turned half way {now}")
#avoid the list filling too much
start_read_span = now-read_span_sec
if len(half_turns) > 0 and half_turns[0] < start_read_span:
half_turns.popleft()
GPIO.add_event_detect(12, GPIO.RISING, callback=spin)
def getWindspeed():
global half_turns
start_read_span = datetime.timestamp(datetime.now()) - read_span_sec
while len(half_turns) > 0 and half_turns[0] < start_read_span:
half_turns.popleft()
#print(f"after removing {half_turns}")
#print(f"turns in the past {read_span_sec} sec {len(half_turns)/2}")
windspeed_meter_per_sec = ( half_turns_multiplier * len(half_turns) ) / read_span_sec
return windspeed_meter_per_sec
adc = MCP3008(channel=0)
wind_direction_codes = {12:0, 41:22.5, 36:45, 84:67.5, 82:90, 87:112.5, 68:135, 77:157.5, 54:180, 60:202.5, 22:225, 25:247.5, 3:270, 10:292.5, 6:315, 17:337.5}
def getWinddirection():
wind_direction_measurement = int(adc.value * 100)
#print(f"wind_direction_measurement {wind_direction_measurement}")
for wind_direction_code in wind_direction_codes:
if wind_direction_measurement-1 <= wind_direction_code and wind_direction_code <= wind_direction_measurement+1:
return wind_direction_codes[wind_direction_code]
def getWind():
return {
"speed": round(getWindspeed(), 2),
"direction": getWinddirection()
}
######################## rain
bucket_tips = deque([])
bucket_volume_mm = 0.2794
one_hour_sec = 60 * 60
one_day_sec = one_hour_sec * 24
one_week_sec = one_day_sec * 7
def bucket_tipped(channel):
now_sec = datetime.timestamp(datetime.now())
bucket_tips.append(now_sec)
#print(f"added bucket tip {now_sec}")
one_week_ago = now_sec - one_week_sec
while len(bucket_tips) > 0 and bucket_tips[0] < one_week_ago:
bucket_tips.popleft()
#print(f"removed bucket tips older than a week. ")
with open(file_path, 'wb') as file:
pickle.dump(bucket_tips, file)
#print(f"saved bucket tips to file: {file_path}")
rain_actual = MCP3008(channel=1)
rain_amount = Button(23)
rain_amount.when_pressed = bucket_tipped
def getRain():
now_sec = datetime.timestamp(datetime.now())
one_hour_ago = now_sec - one_hour_sec
one_day_ago = now_sec - one_day_sec
one_week_ago = now_sec - one_week_sec
one_hour_tips = 0
one_day_tips = 0
one_week_tips = 0
for bucket_tip in bucket_tips:
if bucket_tip > one_hour_ago:
one_hour_tips += 1
if bucket_tip > one_day_ago:
one_day_tips += 1
if bucket_tip > one_week_ago:
one_week_tips += 1
#print(f"tips in the past hour: {one_hour_tips}, day: {one_day_tips}, week: {one_week_tips}")
rain = {
"actual": round((1-rain_actual.value)*100),
"hour": round(one_hour_tips * bucket_volume_mm, 2),
"day": round(one_day_tips * bucket_volume_mm, 2),
"week": round(one_week_tips * bucket_volume_mm, 2),
}
return rain
######################## init stuff
def initialize():
global bucket_tips
try:
with open(file_path, 'rb') as file:
bucket_tips = pickle.load(file)
except:
print(f"could not read file {file_path}")
print("initialize finished")
initialize()
app = Flask(__name__)
######################## REST controller
@app.get("/values")
def values():
air = getAir()
rain = getRain()
wind = getWind()
updated = datetime.now().strftime("%Y-%m-%d, %H:%M:%S")
print(f"values - updated: {updated}, air: {air}, rain: {rain}, wind: {wind}, ")
x = {
"updated": updated,
"air": air,
"wind": wind,
"rain": rain
}
return json.dumps(x)

Die Elektronik habe ich auf ein Perma-Proto Board gelötet, welches auf den 40-Pin Header vom Raspberry PI Zero gesteckt werden kann. Die Pins kann man dann auf einfache Weise mit anderen Komponenten verbinden.

So habe ich alle Sensoren mit dem Board verbunden und für die Anschlüsse ausserhalb sind zwei RJ11 Buchsen montiert. Diese sind so platziert, dass kein Wasser eindringen kann.

