To learn how to create a weather station with Raspberry Pi, you need three core elements: a reliable single-board computer, a hardware-I2C environmental sensor, and a lightweight messaging protocol to log the data. This guide targets the Raspberry Pi 4 Model B (4GB variant) running Raspberry Pi OS (Bookworm 64-bit). We will interface a Bosch BME280 sensor to read temperature, humidity, and barometric pressure, and push that telemetry to an MQTT broker using Python.
Time to Build: 45 minutes for hardware, 30 minutes for software and broker configuration.
Hardware Spec Sheet & Parts List
Generic sensor clones often cause I2C address conflicts and lack proper pull-up resistors. The bill of materials below specifies exact variants to ensure bench-level reliability.
| Component | Exact Variant / Model | 2026 Est. Price | Engineering Notes |
|---|---|---|---|
| Microcontroller | Raspberry Pi 4 Model B (4GB) | $55.00 | Pi 5 works identically for I2C, but Pi 4 runs cooler, reducing thermal interference with the sensor. |
| Sensor | Adafruit BME280 (PID 2652) | $14.95 | Includes onboard 3.3V LDO and 10kΩ I2C pull-ups. Generic clones default to a different I2C address. |
| Wiring | 28 AWG Female-to-Female Jumpers | $4.00 | Keep I2C runs under 30cm to avoid capacitance-induced signal degradation. |
| Storage | SanDisk Extreme 32GB microSD | $9.00 | High endurance (A2 rated) to survive frequent OS logging and swap writes. |
| Enclosure | Stevenson Screen (3D printed or purchased) | $15.00 | Mandatory for outdoor use to block solar radiation while allowing airflow. |
Pin Mapping & Physical Wiring
The BME280 communicates via I2C. We will use the Raspberry Pi's primary hardware I2C bus (I2C1). Do not use software-bit-banged I2C for environmental logging; OS thread scheduling delays will cause dropped reads.
| BME280 Breakout Pin | Pi 4 GPIO (BCM) | Pi Physical Pin | Recommended Wire Color |
|---|---|---|---|
| VIN (or 3Vo) | 3.3V Power | Pin 1 | Red |
| GND | Ground | Pin 6 | Black |
| SCL | GPIO 3 (SCL) | Pin 5 | Yellow |
| SDA | GPIO 2 (SDA) | Pin 3 | Blue |
- De-energize the Pi: Disconnect the USB-C power supply before wiring the GPIO header.
- Connect the I2C Lines: Map SDA to Pin 3 and SCL to Pin 5. Double-check these; swapping them won't fry the board, but it will halt communication.
- Enable I2C in OS: Boot the Pi, open the terminal, and run
sudo raspi-config. Navigate to Interface Options > I2C and enable it. Reboot. - Verify Hardware Address: Run
sudo i2cdetect -y 1. You should see77in the grid (Adafruit breakouts default to 0x77; cheap clones often show76).
Python MQTT Code with Error Handling
This script uses the adafruit-circuitpython-bme280 library for sensor abstraction and paho-mqtt for telemetry. Note: This code is written for Paho MQTT v2.0+, which changed the on_connect callback signature. Using v1.x syntax will throw a TypeError in current environments.
pip install adafruit-circuitpython-bme280 paho-mqtt
import board
import busio
import adafruit_bme280
import paho.mqtt.client as mqtt
import time
import json
# --- PIN DEFINITIONS (BCM mapping via Blinka) ---
# SDA maps to GPIO 2 (Physical Pin 3)
# SCL maps to GPIO 3 (Physical Pin 5)
I2C_SDA = board.SDA
I2C_SCL = board.SCL
# --- NETWORK CONFIGURATION ---
MQTT_BROKER = "192.168.1.100" # Replace with your Mosquitto broker IP
MQTT_PORT = 1883
MQTT_TOPIC = "home/weather/outside"
# Paho MQTT 2.0+ Callback Signature (includes reason_code and properties)
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
print("[MQTT] Connected to Broker successfully.")
else:
print(f"[MQTT] Connection failed with reason code: {reason_code}")
# --- SENSOR INITIALIZATION ---
try:
i2c = busio.I2C(I2C_SCL, I2C_SDA)
# Adafruit breakouts use 0x77. Change to 0x76 if using generic clones.
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
# Set oversampling to reduce noise without oversaturating the I2C bus
bme280.oversampling_humidity = adafruit_bme280.OVERSAMPLING_X2
bme280.oversampling_temperature = adafruit_bme280.OVERSAMPLING_X2
print("[SENSOR] BME280 initialized on I2C bus 1.")
except ValueError as e:
print(f"[FATAL] I2C Init Error: {e}")
exit(1)
except OSError as e:
print(f"[FATAL] I2C Bus Error: {e}. Is I2C enabled in raspi-config?")
exit(1)
# --- MQTT CLIENT SETUP ---
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
try:
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start() # Runs network loop in a background thread
except Exception as e:
print(f"[FATAL] MQTT Connection Error: {e}")
exit(1)
# --- MAIN TELEMETRY LOOP ---
try:
while True:
try:
payload = {
"temp_c": round(bme280.temperature, 2),
"humidity": round(bme280.humidity, 2),
"pressure_hpa": round(bme280.pressure, 2),
"altitude_m": round(bme280.altitude, 1)
}
# QoS 1 ensures delivery to the broker at least once
client.publish(MQTT_TOPIC, json.dumps(payload), qos=1)
print(f"[TX] Published: {payload}")
time.sleep(60) # 1-minute sample rate prevents sensor self-heating
except Exception as e:
print(f"[ERROR] Read/Publish fault: {e}. Retrying in 10s...")
time.sleep(10)
except KeyboardInterrupt:
print("\n[SYSTEM] Halting telemetry loop.")
client.loop_stop()
client.disconnect()
Debugging: First Three Things to Check When It Fails
When deploying embedded I2C sensors on a multitasking OS like Linux, hardware faults manifest as specific Python exceptions. Here are the exact error strings and how to fix them.
1. ValueError: No I2C device at address: 0x77
Ranked Causes:
- Clone Address Mismatch: You are using a generic eBay/Amazon BME280 breakout. These often tie the SDO pin to GND, shifting the address to
0x76. Fix: Changeaddress=0x77toaddress=0x76in the Python script. - Missing Pull-ups: Your breakout board lacks onboard I2C pull-up resistors, and the Pi's internal pull-ups are too weak for the bus capacitance. Fix: Solder 4.7kΩ resistors between SDA/VCC and SCL/VCC.
2. OSError: [Errno 121] Remote I/O error
Ranked Causes:
- I2C Not Enabled: You skipped the
raspi-configstep. Fix: Runsudo raspi-config, enable I2C, and reboot. - Loose Breadboard Contact: Female-to-female jumpers on cheap breadboards often lose tension. Fix: Squeeze the female connector ends with pliers or solder the header directly.
- Baud Rate Too High: The Pi defaults to 100kHz I2C clock. If wiring is long (>30cm), signal edges degrade. Fix: Add
dtparam=i2c_baudrate=50000to/boot/firmware/config.txtto halve the speed.
3. ConnectionRefusedError: [Errno 111] Connection refused
Ranked Causes:
- Broker Offline: Mosquitto isn't running on the target IP. Fix: SSH into the broker and run
sudo systemctl status mosquitto. - Firewall Block: UFW or iptables is blocking port 1883. Fix: Run
sudo ufw allow 1883/tcpon the broker machine.
Extending vs. Simplifying the Build
Depending on your deployment environment, you may need to scale this architecture up for commercial-grade logging or down for low-power off-grid use.
paho-mqtt logic with Python's built-in csv module to append readings to a local file on the SD card. Swap the Pi 4 for a Raspberry Pi Zero 2 W ($15) to cut power consumption from 3.5W down to 1.2W, making it viable for small solar setups.
FAQ: Long-Tail Weather Station Questions
How to create a weather station with Raspberry Pi without WiFi?
If your deployment site lacks WiFi (e.g., a remote agricultural field), you have two reliable alternatives. First, use a LoRaWAN HAT (like the Dragino LoRa/GPS HAT) to transmit small JSON payloads over several kilometers to a gateway. Second, use a wired Ethernet connection with a PoE (Power over Ethernet) splitter, which provides both network backhaul and power over a single CAT6 cable, completely eliminating the need for local power supplies and WiFi antennas.
Why is my Raspberry Pi weather station reading higher temperatures than reality?
This is the most common physics-related failure in Pi weather builds. The Raspberry Pi 4 CPU generates significant heat (often idling at 45°C+). If your BME280 sensor is mounted in the same enclosure as the Pi, or directly above it on a short breadboard, the sensor will read the Pi's thermal exhaust, not the ambient air. The fix: Mount the BME280 outside the main enclosure inside a louvered Stevenson screen, connected via a shielded 4-core cable, ensuring at least 1 meter of physical separation from the Pi's CPU heat sink.
Can I use a DHT22 instead of a BME280 for this Raspberry Pi weather station?
While physically possible, it is highly discouraged for Linux-based systems. The DHT22 uses a proprietary one-wire protocol that requires precise microsecond bit-banging. Because Raspberry Pi OS is not a real-time operating system (RTOS), background tasks (like cron jobs, network polling, or logging) will interrupt the CPU, causing the DHT22 timing to fail and returning None or checksum errors. The BME280 uses hardware I2C, meaning the Pi's dedicated I2C peripheral handles the timing independently of the main CPU threads, guaranteeing reliable reads regardless of OS load.






