Home Assistant

Station météo Raspberry Pi avec BME280 et historique SQLite : tuto + code

Construire une station météo intérieure ou abritée avec un BME280 sur Raspberry Pi 4 ou 5 : câblage I2C, code Python complet, historique SQLite, résumé quotidien et export CSV.

Illustration d'un Raspberry Pi relié à un capteur BME280 avec une courbe d'historique de température
Illustration générée par IA

Le BME280 mesure température, humidité et pression atmosphérique. Avec un Raspberry Pi, il devient une petite station météo qui garde l’historique dans une base SQLite : vous pouvez ensuite consulter les minimums et maximums de chaque jour, ou exporter les données en CSV pour un tableur. La pression qui baisse vite reste un bon indice d’arrivée de dégradation.

Objectif

  • Enregistrer une mesure toutes les 5 minutes dans ~/meteo.db.
  • Afficher un résumé quotidien (min, max, moyenne) en une commande.
  • Exporter tout l’historique en CSV.

Durée, niveau et budget

DuréeNiveauBudget
1 hdébutant à intermédiairele BME280 en module générique est l’un des capteurs I2C les plus abordables (prix à vérifier sur le lien) ; aucun abonnement, les données restent chez vous

Aucun prix n’est indiqué ici car il varie : vérifiez-le au moment de l’achat. Si vous possédez déjà le matériel, ce tutoriel ne demande aucun achat.

Les étapes en un coup d’œil

  1. Activer l’I2C
  2. Câbler le BME280 en 3,3 V
  3. Repérer l’adresse 0x76 ou 0x77
  4. Installer la bibliothèque dans le venv
  5. Renseigner l’altitude et lancer l’enregistrement
  6. Consulter le résumé quotidien
  7. Exporter en CSV
  8. Installer le service systemd

Matériel

  • Raspberry Pi 4 ou 5, Raspberry Pi OS Bookworm.
  • Un module BME280 en I2C (modules BME280 sur Amazon). Attention : des modules « BMP280 » sans humidité sont parfois vendus sous le même nom ; le BMP280 ne fonctionnera pas avec ce code.
  • 4 fils Dupont. Pour une mesure extérieure, un abri ventilé à l’ombre.

Câblage

Module BME280Raspberry Pi (broche physique)Rôle
VIN (ou VCC)broche 1 (3V3)alimentation 3,3 V
GNDbroche 6 (GND)masse
SDA (ou SDI)broche 3 (GPIO2 / SDA1)données I2C
SCL (ou SCK)broche 5 (GPIO3 / SCL1)horloge I2C

L’adresse I2C est 0x77 par défaut chez Adafruit, mais beaucoup de modules génériques sont en 0x76. i2cdetect vous le dira.

Préparer le Raspberry Pi (commun à tous les tutoriels)

Système visé : Raspberry Pi OS Bookworm (Pi 4 ou Pi 5). Sur Bookworm, pip install hors environnement virtuel est bloqué : on crée donc un venv. On y donne accès aux paquets système (--system-site-packages) pour profiter de gpiozero et lgpio installés par apt, qui fonctionnent sur Pi 4 et Pi 5 (la bibliothèque RPi.GPIO ne gère pas le GPIO du Pi 5).

sudo apt update
sudo apt install -y python3-venv python3-gpiozero python3-lgpio i2c-tools
mkdir -p ~/capteurs && cd ~/capteurs
python3 -m venv --system-site-packages venv
source venv/bin/activate

Activez ensuite le bus I2C : sudo raspi-config nonint do_i2c 0 (ou Interface Options → I2C dans sudo raspi-config), puis redémarrez. Vérifiez que le module est vu :

i2cdetect -y 1

Vous devez voir 76 ou 77. Si c’est 76, lancez le script avec BME280_ADDR=0x76.

pip install adafruit-circuitpython-bme280

Code complet

Télécharger meteo_bme280_sqlite.py (même code que ci-dessous).

#!/usr/bin/env python3
"""Station météo BME280 sur Raspberry Pi avec historique SQLite.

Matériel : Raspberry Pi 4 ou 5 (Bookworm), module BME280 en I2C (0x77 ou 0x76 selon le module).
Dépendance (dans un venv) : adafruit-circuitpython-bme280.
Usage :
  python meteo_bme280_sqlite.py            -> enregistre une mesure toutes les INTERVAL secondes
  python meteo_bme280_sqlite.py --resume   -> min / max / moyenne par jour (7 derniers jours)
  python meteo_bme280_sqlite.py --csv      -> export CSV sur la sortie standard
Source : robot-educatif.info - tutoriel « Station météo BME280 avec historique ».
"""
import argparse
import csv
import os
import sqlite3
import sys
import time
from datetime import datetime, timezone

DB_PATH = os.getenv("DB_PATH", os.path.expanduser("~/meteo.db"))
INTERVAL = int(os.getenv("INTERVAL", "300"))           # 5 minutes
I2C_ADDR = int(os.getenv("BME280_ADDR", "0x77"), 16)    # mettre 0x76 si i2cdetect l'indique
ALTITUDE_M = float(os.getenv("ALTITUDE_M", "0"))        # altitude du lieu pour la pression mer

SCHEMA = """CREATE TABLE IF NOT EXISTS mesures (
    ts TEXT PRIMARY KEY,          -- horodatage ISO 8601 UTC
    temperature REAL,             -- °C
    humidite REAL,                -- %
    pression REAL                 -- hPa ramenée au niveau de la mer
)"""


def ouvrir_db():
    db = sqlite3.connect(DB_PATH)
    db.execute(SCHEMA)
    return db


def pression_niveau_mer(p_hpa, altitude_m):
    """Formule barométrique internationale (approximation standard)."""
    return p_hpa / (1 - altitude_m / 44330.0) ** 5.255


def enregistrer():
    import board
    from adafruit_bme280 import basic as adafruit_bme280

    capteur = adafruit_bme280.Adafruit_BME280_I2C(board.I2C(), address=I2C_ADDR)
    db = ouvrir_db()
    print(f"Enregistrement dans {DB_PATH} toutes les {INTERVAL} s", flush=True)
    while True:
        ts = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
        t = round(capteur.temperature, 2)
        h = round(capteur.relative_humidity, 1)
        p = round(pression_niveau_mer(capteur.pressure, ALTITUDE_M), 1)
        with db:                                   # commit automatique
            db.execute("INSERT OR REPLACE INTO mesures VALUES (?,?,?,?)", (ts, t, h, p))
        print(ts, t, "°C", h, "%", p, "hPa", flush=True)
        time.sleep(INTERVAL)


def resume():
    db = ouvrir_db()
    rows = db.execute("""SELECT substr(ts,1,10) AS jour,
               ROUND(MIN(temperature),1), ROUND(MAX(temperature),1), ROUND(AVG(temperature),1),
               ROUND(AVG(humidite),0), ROUND(AVG(pression),1), COUNT(*)
        FROM mesures GROUP BY jour ORDER BY jour DESC LIMIT 7""").fetchall()
    print("jour        tmin  tmax  tmoy  hum%  hPa     n")
    for r in rows:
        print("{}  {:>5} {:>5} {:>5} {:>4} {:>7} {:>4}".format(*r))


def export_csv():
    db = ouvrir_db()
    w = csv.writer(sys.stdout)
    w.writerow(["ts", "temperature", "humidite", "pression"])
    w.writerows(db.execute("SELECT * FROM mesures ORDER BY ts"))


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--resume", action="store_true")
    ap.add_argument("--csv", action="store_true")
    a = ap.parse_args()
    if a.resume:
        resume()
    elif a.csv:
        export_csv()
    else:
        enregistrer()

Utilisation :

ALTITUDE_M=150 python meteo_bme280_sqlite.py      # enregistrement (Ctrl+C pour arrêter)
python meteo_bme280_sqlite.py --resume            # min/max/moyenne des 7 derniers jours
python meteo_bme280_sqlite.py --csv > meteo.csv   # export pour un tableur

ALTITUDE_M sert à ramener la pression au niveau de la mer, comme les bulletins météo ; indiquez l’altitude de votre logement.

Lancement automatique avec systemd

Créez le fichier /etc/systemd/system/station-meteo.service (adaptez l’utilisateur pi et le chemin) :

[Unit]
Description=station-meteo (robot-educatif.info)
After=network-online.target
Wants=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/capteurs
Environment=ALTITUDE_M=150
Environment=BME280_ADDR=0x76
ExecStart=/home/pi/capteurs/venv/bin/python /home/pi/capteurs/meteo_bme280_sqlite.py
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now station-meteo.service
journalctl -u station-meteo.service -f    # suivre les messages du script

L’astuce de ce tutoriel : la tendance barométrique sur 3 heures

Les météorologues regardent l’évolution de la pression sur 3 heures plutôt que sa valeur. Votre historique SQLite permet ce calcul en une requête :

sqlite3 ~/meteo.db "SELECT ROUND((SELECT pression FROM mesures ORDER BY ts DESC LIMIT 1) - (SELECT pression FROM mesures WHERE ts <= strftime('%Y-%m-%dT%H:%M:%S', 'now', '-3 hours') ORDER BY ts DESC LIMIT 1), 1) AS tendance_hPa_3h;"

Un résultat nettement négatif indique une pression qui chute ; positif, qu’elle remonte. Interprétez-le comme un indice local, pas comme une prévision. Ce calcul marche aussi sur un historique exporté d’une autre station.

Erreurs fréquentes

  • Confondre BMP280 et BME280 (pas d’humidité).
  • Oublier ALTITUDE_M : la pression affichée sera inférieure à celle des bulletins.
  • Coller le module au Pi : il mesure la chaleur du processeur.
  • Exposer le module au soleil direct dehors.

Dépannage

  • Rien en 0x76/0x77 : câblage SDA/SCL, I2C désactivé.
  • RuntimeError: Failed to find BME280! Chip ID 0x58 : c’est un BMP280 (pas d’humidité).
  • Température trop haute de quelques degrés : le module est trop près du Pi ; éloignez-le d’au moins 10 cm.
  • database is locked : ne lancez pas deux enregistreurs sur la même base.
  • Carte SD : une écriture toutes les 5 minutes reste très modérée ; évitez de descendre sous la minute.

Comment ce code a été vérifié : nous n’avons pas monté ce capteur nous-mêmes. Le script a été contrôlé (syntaxe, installation des dépendances dans un venv, exécution) avec un capteur et un broker MQTT simulés, et chaque broche, adresse et fonction a été vérifiée dans les documentations officielles citées en fin d’article. Si un détail diffère sur votre module, la section Dépannage vous aide à le trouver.

Sources vérifiées

À lire aussi : la version ESP32 de ce capteur, capteur température ESP32 + ESPHome, et le capteur CO2 SCD41 sur Raspberry Pi.