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.