Raspberry Pi Workshop: I2C Mini-Wetterstation mit BME280, OLED & Systemd-Telemetrie
Erstellt: 06.11.2026 Aktualisiert: 06.11.2026
Management Summary: Günstige Consumer-Wettersensoren basieren häufig auf langsamen, bit-gebangten 1-Wire-Protokollen (
DHT11/DHT22), die unter dem Scheduling-Jitter von Linux leicht Checksummen verlieren. In diesem Engineering-Workshop bauen wir eine kalibriergenaue I2C-Umwelt-Telemetriestation auf einem Raspberry Pi (Debian 12 Bookworm 64-Bit) mit dem BoschBME280MEMS-Sensor (Messung von Temperatur, relativer Feuchte und barometrischem Luftdruck über Hardware-I2C1), berechnen meteorologischen Taupunkt und Meereshöhendruck (QNH) und speichern Zeitreihendaten atomar in SQLite (WAL-Modus) über einen überwachtensystemd-Dienst.
-
Hardware
I2C1-Bus-Adressierung
Sowohl der Bosch
BME280(0x76) als auch ein optionalesSSD1306OLED (0x3c) werden über dieselben zweiSDA/SCL-Leitungen (GPIO 2&GPIO 3) betrieben. -
Meteorologische Physik
Exakte Ableitung des Magnus-Tetens-Taupunkts (
°C) und des höhenkompensierten Meereshöhendrucks (QNH hPa) aus den rohen Stationsmesswerten. -
SQLite WAL-Zeitreihenspeicher
Atomares Logging mit Zeitstempel ohne Risiko einer SD-Karten-Korruption mittels SQLite
PRAGMA journal_mode=WAL. -
Autonomer
systemd-Daemon
Betrieb des Telemetrie-Kollektors unter einem dedizierten, unprivilegierten Service-Account mit automatischer Ausfallsicherung.
1. Hardware-Architektur & Warum der BME280 dem DHT22 überlegen ist
Verwende auf einem Multitasking-Linux-Betriebssystem niemals veraltete DHT11/DHT22-Sensoren: Sie erfordern Mikrosekunden-Bit-Banging im User-Space, besitzen keinen Luftdrucksensor und driften bei dauerhaft hoher Luftfeuchtigkeit stark ab. Der Bosch BME280 bietet dagegen einen hardwarebasierten I2C/SPI-Controller auf dem Chip, werkseitig kalibrierte Trimm-Register und eine Druckgenauigkeit von ±1,0 hPa.
%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
subgraph PiHeader ["📌 Raspberry Pi 40-Pin Header"]
P1["Pin 1: 3,3V DC Stromversorgung"]
P3["Pin 3: GPIO 2 (I2C1 SDA)"]
P5["Pin 5: GPIO 3 (I2C1 SCL)"]
P6["Pin 6: Masse (GND)"]
end
subgraph I2CBus ["🔗 Gemeinsamer 400 kHz I2C1-Bus"]
BME["🌡️ Bosch BME280 Sensor<br/>I2C-Adresse: 0x76 (oder 0x77)<br/>Temp / Feuchte / Luftdruck"]
OLED["🖥️ SSD1306 128x64 OLED<br/>I2C-Adresse: 0x3c<br/>Lokale Live-Anzeige"]
end
P1 --> BME
P1 --> OLED
P3 <-->|"SDA-Daten"| BME
P3 <-->|"SDA-Daten"| OLED
P5 -->|"SCL-Takt"| BME
P5 -->|"SCL-Takt"| OLED
P6 --- BME
P6 --- OLED
Pin-Verdrahtungsmatrix (Ausschließlich 3,3V Logikpegel!)
BME280 / SSD1306 Pin |
Physischer Raspberry Pi Pin | BCM GPIO Name | Farbbelegung (Empfehlung) | Elektrische Funktion |
|---|---|---|---|---|
VCC / VIN |
Pin 1 (oder Pin 17) | 3V3 Power |
🔴 Rot | Streng geregelte 3,3V DC Stromversorgung |
GND |
Pin 6 (oder Pin 9, 14, 20) | Ground |
⚫ Schwarz | Gemeinsame Signal- und Versorgungsmasse |
SDA / SDI |
Pin 3 | GPIO 2 (SDA1) |
🔵 Blau | Bidirektionale I2C Serial Data Leitung (interner 1,8 kΩ Pull-up auf dem Pi) |
SCL / SCK |
Pin 5 | GPIO 3 (SCL1) |
🟡 Gelb | Synchronisierte I2C Serial Clock Leitung (400 kHz Fast-Mode) |
2. Hardware-I2C1 aktivieren & Bus-Adressen verifizieren
Aktiviere den ARM-Hardware-I2C-Controller in /boot/firmware/config.txt und prüfe mit i2cdetect, ob der Sensor auf Bus 1 (/dev/i2c-1) antwortet:
# Datei: /opt/pi-weather/01_enable_verify_i2c.sh
#!/usr/bin/env bash
set -euo pipefail
# 1. I2C-Diagnose-Utilities und Python-Umgebungswerkzeuge installieren
sudo apt update && sudo apt install -y i2c-tools python3-venv python3-pip sqlite3
# 2. Dem aktuellen Benutzer Root-freie Rechte auf /dev/i2c-* gewähren
sudo usermod -aG i2c "$USER"
# 3. I2C-Bus 1 scannen (0x76 oder 0x77 für BME280, 0x3c bei angeschlossenem OLED erwartet)
sudo i2cdetect -y 1
Erwartete Ausgabe von sudo i2cdetect -y 1:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
3. Meteorologische Physik & SQLite-Telemetrie-Kollektor
Der rohe Sensordruck (Stationsdruck) fällt um ca. 12 hPa pro 100 Meter Höhe über dem Meeresspiegel ab. Um den barometrischen Trend deiner Station mit offiziellen Wetter- und Luftfahrtkarten vergleichen zu können, berechnen wir den Meereshöhendruck (QNH) mittels der internationalen barometrischen Höhenformel und den Taupunkt (°C) über die Magnus-Tetens-Formel.
Erstelle eine isolierte virtuelle Python-Umgebung in /opt/pi-weather und installiere RPi.bme280 sowie smbus2:
# Datei: /opt/pi-weather/02_setup_venv.sh
sudo mkdir -p /opt/pi-weather /var/lib/pi-weather
sudo chown -R "$USER:$USER" /opt/pi-weather /var/lib/pi-weather
python3 -m venv /opt/pi-weather/.venv
/opt/pi-weather/.venv/bin/pip install --upgrade pip smbus2 RPi.bme280
Erstelle nun das produktive Telemetrie-Kollektorskript (/opt/pi-weather/weather_collector.py):
# Datei: /opt/pi-weather/weather_collector.py
#!/usr/bin/env python3
"""Produktionsreifer I2C BME280 Telemetrie-Kollektor mit Taupunkt, QNH-Druck und SQLite-WAL-Speicherung."""
import math
import sqlite3
import time
from datetime import datetime, timezone
from pathlib import Path
import bme280
import smbus2
I2C_BUS = 1
I2C_ADDRESS = 0x76
STATION_ALTITUDE_M = 115.0 # Auf die exakte lokale Höhe über dem Meeresspiegel (Meter) setzen
DB_PATH = Path("/var/lib/pi-weather/weather_telemetry.sqlite3")
SAMPLE_INTERVAL_SEC = 60
def calculate_dew_point_c(temp_c: float, humidity_pct: float) -> float:
"""Taupunkt (°C) über die Magnus-Tetens-Formel berechnen."""
a, b = 17.625, 243.04
alpha = math.log(max(humidity_pct, 0.1) / 100.0) + (a * temp_c) / (b + temp_c)
return round((b * alpha) / (a - alpha), 2)
def calculate_sea_level_qnh(station_hpa: float, temp_c: float, altitude_m: float) -> float:
"""Lokalen Stationsdruck (hPa) auf Meereshöhendruck (QNH hPa) normieren."""
temp_kelvin = temp_c + 273.15
qnh = station_hpa * math.pow(
1.0 - (0.0065 * altitude_m) / (temp_kelvin + 0.0065 * altitude_m), -5.257
)
return round(qnh, 2)
def init_database(db_path: Path) -> sqlite3.Connection:
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=NORMAL;")
conn.execute(
"""
CREATE TABLE IF NOT EXISTS sensor_readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
recorded_utc TEXT NOT NULL,
temp_c REAL NOT NULL,
humidity_pct REAL NOT NULL,
dew_point_c REAL NOT NULL,
station_hpa REAL NOT NULL,
qnh_hpa REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_readings_utc ON sensor_readings (recorded_utc);
"""
)
conn.commit()
return conn
def run_collector() -> None:
bus = smbus2.SMBus(I2C_BUS)
calibration_params = bme280.load_calibration_params(bus, I2C_ADDRESS)
conn = init_database(DB_PATH)
print(f"[INFO] BME280 initialisiert an 0x{I2C_ADDRESS:02x}. Logging nach {DB_PATH}...")
try:
while True:
sample = bme280.sample(bus, I2C_ADDRESS, calibration_params)
temp_c = round(sample.temperature, 2)
humidity_pct = round(sample.humidity, 2)
station_hpa = round(sample.pressure, 2)
dew_c = calculate_dew_point_c(temp_c, humidity_pct)
qnh_hpa = calculate_sea_level_qnh(station_hpa, temp_c, STATION_ALTITUDE_M)
now_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
conn.execute(
"""
INSERT INTO sensor_readings
(recorded_utc, temp_c, humidity_pct, dew_point_c, station_hpa, qnh_hpa)
VALUES (?, ?, ?, ?, ?, ?)
""",
(now_iso, temp_c, humidity_pct, dew_c, station_hpa, qnh_hpa),
)
conn.commit()
print(
f"[{now_iso}] Temp: {temp_c:5.2f}°C | RH: {humidity_pct:5.1f}% | "
f"Taupunkt: {dew_c:5.2f}°C | QNH: {qnh_hpa:7.2f} hPa"
)
time.sleep(SAMPLE_INTERVAL_SEC)
finally:
conn.close()
bus.close()
if __name__ == "__main__":
run_collector()
4. Bereitstellung als gehärteter systemd-Hintergrunddienst
Erstelle eine systemd-Unit, damit der Kollektor automatisch beim Systemstart anläuft und bei I2C-Spannungsschwankungen sauber neu startet:
# Datei: /etc/systemd/system/pi-weather.service
[Unit]
Description=Raspberry Pi I2C BME280 Weather Telemetry Collector
After=local-fs.target
[Service]
Type=simple
User=nejat
SupplementaryGroups=i2c gpio
WorkingDirectory=/opt/pi-weather
ExecStart=/opt/pi-weather/.venv/bin/python /opt/pi-weather/weather_collector.py
Restart=on-failure
RestartSec=10
# Security-Sandboxing
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/lib/pi-weather
[Install]
WantedBy=multi-user.target
Dienst aktivieren und Live-Telemetrielogs einsehen:
# Datei: /opt/pi-weather/03_enable_service.sh
sudo systemctl daemon-reload
sudo systemctl enable --now pi-weather.service
journalctl -u pi-weather.service -f
5. Hardware-Troubleshooting-Matrix
| Symptom / Fehlermeldung | Ursache | Sofortmaßnahme |
|---|---|---|
i2cdetect -y 1 zeigt nur leere -- Felder |
SDA/SCL-Leitungen vertauscht, fehlende Masse (GND) oder 3,3V nicht verbunden |
Physische Pins mit Multimeter prüfen: Pin 1 (3,3V), Pin 3 (SDA), Pin 5 (SCL) und Pin 6 (GND). |
OSError: [Errno 121] Remote I/O error |
Sensoradresse ist 0x77 statt 0x76 (wird über den SDO-Pin des Breakouts gesteuert) |
Ausgabe von i2cdetect -y 1 prüfen und I2C_ADDRESS = 0x77 in weather_collector.py anpassen. |
Temperaturwert liegt 2°C–4°C über dem Raumthermometer |
Sensor sitzt direkt über dem heißen CPU-Kühlkörper des Raspberry Pi | Ein 20 cm langes geschirmtes 4-Draht-Kabel verwenden, um den BME280 außerhalb der thermischen Abwärme des Pi zu platzieren. |
Weiterführende Workshops & Architektur
Streame deine SQLite-Wetterdatenbank auf ein Live-Web-Dashboard oder automatisiere Zeitrafferaufnahmen:
-
Workshop: IoT-Sensor-Webserver
SQLite-Sensordatenbank über eine Echtzeit-JSON-REST-API und ein interaktives Dashboard in Python bereitstellen.
-
Workshop: Zeitraffer-Kamera-Rig
Wetterstation mit automatisierter Wolken- und Himmelsfotografie via Raspberry Pi Camera und
ffmpegkombinieren. -
Raspberry Pi Engineering-Architektur
ARM64-SoC-Architektur, Booten von NVMe-SSDs und
vcgencmd-Spannungs- und Temperaturdiagnose.