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.
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 software handles real-time sensor readings and LCD updates
- Host-side logger manages timestamping, persistence, and version control
- GitHub acts as a remote data archive for long-term storage
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