← Back to projects

Arduino Weather Station

Description

WARNING: This has been deprecated. CWS V2 is now available here
This project is a modular Arduino-based weather station that collects temperature, pressure, and humidity data using a BME280 environmental sensor. Readings are displayed locally on a 16×2 LCD while structured sensor data is streamed over the serial port for long-term logging.

A Python-based serial logger runs on a host machine, capturing sensor output once per minute, timestamping each entry, and organizing logs into a hierarchical directory structure (year / month / week / day). Log files are committed and pushed to a dedicated GitHub repository every 30 minutes, enabling reliable archival and remote access to historical weather data.

Logged Data

Technical Overview

Hardware Components:
1× Arduino UNO
1× BME280 Temperature / Pressure / Humidity Sensor
1× 16×2 Character LCD
1× Potentiometer (LCD contrast)
1× 10KΩ resistor

Pin Connections:
LCD DB4–DB7 → Arduino pins 5, 4, 3, 2
LCD RS → pin 12
LCD E → pin 11
BME280 SDA/SCL → Arduino A4/A5 (I²C)

System Responsibilities:

Arduino Programming

Click to expand

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <LiquidCrystal.h>

Adafruit_BME280 bme;

unsigned long delayTime = 3000;
unsigned long lastLogTime = 0;
const unsigned long LOG_INTERVAL = 60000;

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  Serial.begin(9600);
  lcd.begin(16, 2);

  if (!bme.begin(0x76)) {
    while (1);
  }
}

void loop() {
  float tempC = bme.readTemperature();
  float tempF = tempC * 1.8 + 32;
  float pressure = bme.readPressure() / 100.0F;
  float humidity = bme.readHumidity();

  updateLCD(tempC, tempF, pressure, humidity);

  if (millis() - lastLogTime >= LOG_INTERVAL) {
    lastLogTime = millis();
    Serial.print(tempC, 2); Serial.print(",");
    Serial.print(tempF, 2); Serial.print(",");
    Serial.print(pressure, 2); Serial.print(",");
    Serial.println(humidity, 2);
  }
}

void updateLCD(float tC, float tF, float p, float h) {
  lcd.clear();
  lcd.print("Temp:");
  lcd.setCursor(0, 1);
  lcd.print(tC, 1);
  lcd.print("C ");
  lcd.print(tF, 1);
  lcd.print("F");
  delay(delayTime);

  lcd.clear();
  lcd.print("Pressure:");
  lcd.setCursor(0, 1);
  lcd.print(p, 1);
  lcd.print(" hPa");
  delay(delayTime);

  lcd.clear();
  lcd.print("Humidity:");
  lcd.setCursor(0, 1);
  lcd.print(h, 1);
  lcd.print("%");
  delay(delayTime);
}
          

Serial Logger (Run by host machine)

Click to expand

import serial
import os
from datetime import datetime, timedelta
import subprocess
import time
import socket
import tarfile
import shutil

PORT = "/dev/tty.usbmodem1101"
BAUD = 9600
LOG_BASE = os.path.expanduser("~/Github Repos/AWS-Logs")
LOG_INTERVAL = 60
CSV_HEADER = "temp_c,temp_f,pressure_hpa,humidity"
ARCHIVE_DAYS = 30  # compress weeks older than this

ser = serial.Serial(PORT, BAUD, timeout=1)
last_git_push_time = None

def is_online(host="github.com", port=443, timeout=5):
    try:
        socket.create_connection((host, port), timeout=timeout)
        return True
    except OSError:
        return False

def ensure_log_file(path):
    if not os.path.exists(path):
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, "w") as f:
            f.write(CSV_HEADER + "\n")

def log_alert(message):
    now = datetime.now()
    year = str(now.year)
    month = now.strftime("%m_%B")
    week = f"Week_{now.isocalendar()[1]:02d}"
    folder = os.path.join(LOG_BASE, year, month, week)
    os.makedirs(folder, exist_ok=True)
    alert_file = os.path.join(folder, "ALERTS.log")
    timestamp = now.isoformat(timespec="seconds")
    with open(alert_file, "a") as f:
        f.write(f"{timestamp} {message}\n")
    print(message)

def log_data(logfile, line):
    timestamp = datetime.now().isoformat(timespec="seconds")
    with open(logfile, "a") as f:
        f.write(f"{timestamp} {line}\n")
    log_alert(f"[DATA LOGGED] {timestamp} -> {line}")

def git_push():
    now = datetime.now()
    if not is_online():
        log_alert(f"[GIT PUSH SKIPPED] Offline. Will retry at next interval ({now.strftime('%Y-%m-%d %H:%M')})")
        return
    try:
        subprocess.run(["git", "-C", LOG_BASE, "add", "."], check=True)
        subprocess.run([
            "git", "-C", LOG_BASE,
            "commit", "-m",
            f"Automated log update {now.strftime('%Y-%m-%d %H:%M')}"
        ], check=True)
        subprocess.run(["git", "-C", LOG_BASE, "push"], check=True)
        log_alert(f"[GIT PUSH] Successful at {now.strftime('%Y-%m-%d %H:%M')}")
    except subprocess.CalledProcessError as e:
        log_alert(f"[GIT PUSH ERROR] {e}. Will retry next interval.")

def get_log_path():
    now = datetime.now()
    year = str(now.year)
    month = now.strftime("%m_%B")
    week = f"Week_{now.isocalendar()[1]:02d}"
    filename = now.strftime("%Y-%m-%d.log")
    return os.path.join(LOG_BASE, year, month, week, filename)

def next_push_time():
    now = datetime.now()
    next_minute = 0 if now.minute < 30 else 30
    next_hour = now.hour if now.minute < 30 else (now.hour + 1) % 24
    return now.replace(hour=next_hour, minute=next_minute, second=0, microsecond=0)

def compress_old_weeks():
    cutoff = datetime.now() - timedelta(days=ARCHIVE_DAYS)
    for year in os.listdir(LOG_BASE):
        year_path = os.path.join(LOG_BASE, year)
        if not os.path.isdir(year_path):
            continue
        for month in os.listdir(year_path):
            month_path = os.path.join(year_path, month)
            if not os.path.isdir(month_path):
                continue
            for week in os.listdir(month_path):
                week_path = os.path.join(month_path, week)
                if not os.path.isdir(week_path):
                    continue
                # Determine the latest log date in the week
                log_dates = [f for f in os.listdir(week_path) if f.endswith(".log")]
                if not log_dates:
                    continue
                # Use the last log date in week to compare with cutoff
                last_log_file = sorted(log_dates)[-1]
                try:
                    last_log_date = datetime.strptime(last_log_file.replace(".log",""), "%Y-%m-%d")
                except ValueError:
                    continue
                if last_log_date < cutoff:
                    archive_name = week_path + ".tar.gz"
                    with tarfile.open(archive_name, "w:gz") as tar:
                        for f in os.listdir(week_path):
                            tar.add(os.path.join(week_path, f), arcname=f)
                    shutil.rmtree(week_path)
                    log_alert(f"[ARCHIVE] Compressed week folder {week_path} -> {archive_name}")

log_alert("Weather logger started... Press Ctrl+C to stop.")
log_alert(f"Next Git push scheduled at: {next_push_time().strftime('%Y-%m-%d %H:%M')}")

try:
    while True:
        logfile = get_log_path()
        ensure_log_file(logfile)

        line = ser.readline().decode("utf-8", errors="ignore").strip()
        if line:
            log_data(logfile, line)

        compress_old_weeks()

        now = datetime.now()
        interval_id = now.strftime("%Y-%m-%d_%H_%M")
        if now.minute % 30 == 0 and interval_id != last_git_push_time:
            last_git_push_time = interval_id
            git_push()

        if now.minute % 30 == 29:
            time.sleep(1)
        else:
            time.sleep(LOG_INTERVAL)

except KeyboardInterrupt:
    log_alert("Logger stopped by user.")

finally:
    ser.close()
          

Images

Temperature display Pressure display Humidity display