Zum Inhalt

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 Bosch BME280 MEMS-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 überwachten systemd-Dienst.

  • Hardware I2C1-Bus-Adressierung


    Sowohl der Bosch BME280 (0x76) als auch ein optionales SSD1306 OLED (0x3c) werden über dieselben zwei SDA/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.

    Zum IoT-Webserver-Lab

  • Workshop: Zeitraffer-Kamera-Rig


    Wetterstation mit automatisierter Wolken- und Himmelsfotografie via Raspberry Pi Camera und ffmpeg kombinieren.

    Zum Zeitraffer-Lab

  • Raspberry Pi Engineering-Architektur


    ARM64-SoC-Architektur, Booten von NVMe-SSDs und vcgencmd-Spannungs- und Temperaturdiagnose.

    Zur Raspberry Pi Einführung