Build a Raspberry Pi weather station with web dashboard

Sharing is caring!

Build a Raspberry Pi Weather Station with Web Dashboard

Free Daily Electronics Newsletter

Tutorials, news, and one component explained simply — every day.

Subscription Form

Difficulty: Intermediate

Weather radar station set in a field with a clear blue sky and scattered clouds, showcasing meteorological technology.
Photo by Charles Criscuolo on Pexels

Ever wondered what the temperature and humidity actually are in your backyard versus what the weather app claims? Professional weather stations cost hundreds of dollars, but you can build your own internet-connected version for under $50. This Raspberry Pi weather station logs temperature, humidity, and barometric pressure every minute, then displays it all on a sleek web dashboard you can check from anywhere.

Unlike those plastic weather stations from big-box stores that stop working after six months, this build gives you complete control over your data. You’ll learn how I2C sensors communicate, how to build a simple Python web server, and how to create live-updating charts that would make any meteorology hobbyist jealous.

IoT System Overview

Temp Sensor DHT22

Humidity Sensor DHT22

Motion Sensor PIR

Raspberry Pi Data Processing Python Server HTTP API

Dashboard Web Browser

GPIO

HTTP WebSocket

Data Flow: Sensor Input Network Output

Free Daily Electronics Newsletter electronics
Image by United States. Army. Corps of Engineers; United States. Army. Corps of Engineers. Public Affairs Off via Wikimedia Commons (Public domain)

Why Build Your Own Weather Station?

Commercial weather stations either lock you into proprietary cloud services that disappear when the company loses interest, or they’re absurdly expensive. Building your own means you control the data, you can add any sensors you want, and you learn a ton about environmental sensing along the way.

This project combines several crucial electronics skills: reading sensor datasheets (a skill we covered in depth at Reading a datasheet: the skill that unlocks every component), working with I2C communication protocols, Python programming, and building web interfaces. Plus, once you’ve built this, you can easily expand it with rainfall sensors, wind speed anemometers, or even air quality monitors.

The web dashboard approach means you’re not tied to a tiny LCD screen — you can view your weather data on your phone, tablet, or computer. The data logs to a SQLite database, so you can analyze trends over weeks or months. Want to know if your apartment’s humidity is really contributing to that mold problem? Now you’ll have the data to prove it.

Understanding the I2C Sensor Bus

Before we start wiring, let’s talk about I2C (Inter-Integrated Circuit, pronounced “I-squared-C”). This two-wire protocol lets multiple sensors share the same communication lines, which is perfect for a weather station with several sensors.

I2C Bus Configuration

Raspberry Pi Master GPIO 2/3

4.7kΩ

4.7kΩ

VCC (3.3V)

SDA

SCL

Sensor 1 0x48 Temp/Humidity

Sensor 2 0x76 Pressure

Sensor 3 0x68 Accelerometer

GND

Note: All devices share common bus lines Each sensor has unique I2C address

I2C uses just two wires: SDA (data) and SCL (clock). Each sensor has a unique address, so the Raspberry Pi can talk to specific sensors without confusion. Most environmental sensors use I2C because it’s simple and reliable — you’ll see it everywhere in professional equipment.

The Raspberry Pi’s GPIO pins 2 and 3 are dedicated I2C pins with built-in pull-up resistors (those resistors ensure the lines stay at a known voltage when idle). This makes our wiring incredibly simple — we just connect all sensors in parallel to the same two pins.

Detailed view of a Raspberry Pi circuit board with microchips and components.
Photo by Alessandro Oliverio on Pexels

Choosing Your Sensors

For this build, we’re using three sensors that cover the essential weather measurements:

BME280: This Bosch sensor (datasheet) measures temperature, humidity, and barometric pressure in one tiny package. It’s accurate, low-power, and has become the go-to environmental sensor for hobbyists. The I2C address is typically 0x76 or 0x77 depending on the breakout board.

Optional: DS18B20 temperature probe: If you want to measure outdoor temperature while keeping your Raspberry Pi indoors, this waterproof probe (datasheet) uses the 1-Wire protocol. You can run it on a long cable — I’ve successfully used 10-meter cables without signal degradation.

The BME280 alone gives you everything for a functional weather station. The beauty of I2C is that you can always add more sensors later without rewiring — just connect them to the same SDA and SCL lines.

Wiring the Hardware

This is where the project gets satisfyingly simple. The Raspberry Pi does most of the heavy lifting, so our circuit is minimal.

Raspberry Pi GPIO pinout showing pins 1 (3.3V), 2 (SDA), 3 (SCL), 6 (GND) connected to BME280 sensor breakout board
Raspberry Pi GPIO pinout showing pins 1 (3.3V), 2 (SDA), 3 (SCL), 6 (GND) connected to BME280 sensor breakout board

Connect your BME280 breakout board to the Raspberry Pi:

  • VCC (or VIN) to Pin 1 (3.3V) — never use 5V for the BME280, you’ll damage it
  • GND to Pin 6 (Ground)
  • SDA to Pin 3 (GPIO 2, SDA)
  • SCL to Pin 5 (GPIO 3, SCL)

If your breakout board has a CSB pin, connect it to 3.3V to enable I2C mode. Some boards do this internally, so check your board’s documentation — this is exactly why learning to read datasheets matters.

That’s it for wiring. No resistors, no level shifters, no complexity. The Raspberry Pi’s I2C implementation handles everything. If you’re adding the optional DS18B20 probe, that requires one 4.7kΩ pull-up resistor between data and VCC, connected to GPIO 4.

Black-and-white photo of a remote weather station in Glasgow, Montana.
Photo by Charles Criscuolo on Pexels

Setting Up the Raspberry Pi Software

I’m assuming you’re starting with a fresh Raspberry Pi OS installation. If you’re experienced with Linux, this will feel familiar. If you’re new to Raspberry Pi, don’t worry — I’ll walk through every command.

First, enable I2C on your Pi. Open a terminal and run:

sudo raspi-config

Navigate to “Interface Options,” then “I2C,” and enable it. Reboot your Pi.

After rebooting, install the required Python libraries:

sudo apt-get update
sudo apt-get install -y python3-pip python3-smbus i2c-tools
pip3 install Flask RPi.GPIO adafruit-circuitpython-bme280

The i2c-tools package gives you a crucial debugging command. Run this to see if your Pi detects the BME280:

sudo i2cdetect -y 1

You should see a grid with either 76 or 77 highlighted. That’s your sensor’s address. If you see nothing, double-check your wiring — the most common mistake is swapping SDA and SCL.

i2cdetect -y 1

0 1 2 3 4 5 6 7 8 9 a b c d

00:

10:

20:

30:

Run this script with python3 weather_station.py and watch it log readings every minute. Let it run for an hour to collect some baseline data before we build the dashboard — you’ll want actual data to display.

One important note: the BME280’s temperature reading can be 2-3°C higher than ambient if your Raspberry Pi is working hard, because the sensor picks up heat from the Pi’s CPU. For accurate outdoor measurements, either use the DS18B20 probe on a long cable, or mount the BME280 away from the Pi using longer I2C wires (up to 1-2 meters works fine).

Building the Web Dashboard

Now for the fun part — creating a live web interface. We’ll use Flask (a lightweight Python web framework) and Chart.js (a JavaScript charting library) to build something that looks surprisingly professional.

Flask + SQLite + Chart.js Architecture

SQLite Database data.db Persistent Storage

reads

Python Flask Backend app.py • Query database • Process data • API endpoints

serves

JSON API /api/data {“labels”: […], “values”: […]}

fetch()

HTML/JavaScript Frontend index.html + script.js • Fetch JSON data • Render visualizations

renders

Chart.js Interactive Charts

Create a new file called dashboard.py:

from flask import Flask, render_template, jsonify
import sqlite3
from datetime import datetime, timedelta

app = Flask(__name__)

def get_recent_data(hours=24):
    conn = sqlite3.connect('weather_data.db')
    c = conn.cursor()
    cutoff = (datetime.now() - timedelta(hours=hours)).isoformat()
    c.execute("SELECT * FROM readings WHERE timestamp > ? ORDER BY timestamp",
              (cutoff,))
    data = c.fetchall()
    conn.close()
    return data

@app.route('/')
def index():
    return render_template('dashboard.html')

@app.route('/api/data')
def api_data():
    data = get_recent_data(24)
    formatted = {
        'timestamps': [row[0] for row in data],
        'temperature': [row[1] for row in data],
        'humidity': [row[2] for row in data],
        'pressure': [row[3] for row in data]
    }
    return jsonify(formatted)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Create a templates directory, then create templates/dashboard.html:

<!DOCTYPE html>
<html>
<head>
<title>Weather Station Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }
.container { max-width: 1200px; margin: 0 auto; }
.chart-container { background: white; padding: 20px; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; }
.current { display: flex; gap: 20px; margin: 20px 0; }
.metric { background: white; padding: 20px; border-radius: 8px; flex: 1; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.metric-value { font-size: 2em; font-weight: bold; color: #2196F3; }
</style>
</head>
<body>
<div class="container">
<h1>Weather Station Dashboard</h1>

<div class="current">
<div class="metric">
<div>Temperature</div>
<div class="metric-value" id="current-temp">--</div>
</div>
<div class="metric">
<div>Humidity</div>
<div class="metric-value" id="current-humidity">--</div>
</div>
<div class="metric">
<div>Pressure</div>
<div class="metric-value" id="current-pressure">--</div>
</div>
</div>

<div class="chart-container">
<canvas id="tempChart"></canvas>
</div>
<div class="chart-container">
<canvas id="humidityChart"></canvas>
</div>
<div class="chart-container">
<canvas id="pressureChart"></canvas>
</div>
</div>

<script>
function updateDashboard() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
// Update current values
const latest = data.temperature.length - 1;
document.getElementById('current-temp').textContent =
data.temperature[latest].toFixed(1) + '°C';
document.getElementById('current-humidity').textContent =
data.humidity[latest].toFixed(1) + '%';
document.getElementById('current-pressure').textContent =
data.pressure[latest].toFixed(1) + ' hPa';

// Update charts
updateChart(tempChart, data.timestamps, data.temperature);
updateChart(humidityChart, data.timestamps, data.humidity);
updateChart(pressureChart, data.timestamps, data.pressure);
});
}

function createChart(ctx, label, color) {
return new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: label,
data: [],
borderColor: color,
tension: 0.

Free Daily Electronics Newsletter

Tutorials, news, and one component explained simply — every day.

Subscription Form (#5)

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top