Top 5 ESP32 Weather Station Projects (With Code & Circuit)



Top 5 ESP32 Weather Station Projects (With Code & Circuit)

Weather stations are one of the most searched ESP32 project categories — and for good reason. They combine multiple sensors, data logging, web interfaces, and real-world usefulness. These 5 projects go from a simple indoor monitor all the way to a solar-powered outdoor station that pushes data to the cloud.

Project 1 — Indoor Weather Monitor with OLED

Project Description

A BME280 sensor measures temperature, humidity, and atmospheric pressure, displaying all three readings on a 0.96" OLED screen. The screen cycles between a summary view and individual readings every few seconds. Battery-powered, fits in a small enclosure, and looks completely professional on a desk or wall.

Components: ESP32, BME280 sensor, 0.96" I2C OLED (SSD1306), 3.7V LiPo battery, TP4056 module.

Circuit Description

ComponentPinESP32 Pin
BME280SDAGPIO 21
BME280SCLGPIO 22
OLEDSDAGPIO 21 (shared I2C bus)
OLEDSCLGPIO 22 (shared I2C bus)

Both BME280 (address 0x76) and SSD1306 OLED (address 0x3C) share the same I2C bus — no extra wires needed.

Code

#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_SSD1306.h>

Adafruit_BME280 bme;
Adafruit_SSD1306 display(128, 64, &Wire, -1);

int screen = 0;
unsigned long lastSwitch = 0;

void setup() {
  Serial.begin(115200);
  bme.begin(0x76);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(WHITE);
}

void loop() {
  float temp  = bme.readTemperature();
  float hum   = bme.readHumidity();
  float pres  = bme.readPressure() / 100.0F;

  if (millis() - lastSwitch > 3000) { screen = (screen + 1) % 3; lastSwitch = millis(); }

  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);

  if (screen == 0) {
    display.println("-- Weather Station --");
    display.printf("Temp:  %.1f C\n",  temp);
    display.printf("Hum:   %.1f %%\n", hum);
    display.printf("Press: %.1f hPa",  pres);
  } else if (screen == 1) {
    display.println("  Temperature");
    display.setTextSize(3);
    display.setCursor(10, 25);
    display.printf("%.1f", temp);
    display.setTextSize(1); display.setCursor(90, 30); display.println("C");
  } else {
    display.println("    Humidity");
    display.setTextSize(3);
    display.setCursor(10, 25);
    display.printf("%.1f", hum);
    display.setTextSize(1); display.setCursor(90, 30); display.println("%");
  }
  display.display();
  delay(100);
}

Conclusion

The BME280 is the gold-standard sensor for weather monitoring — it's what commercial weather stations use. Next step: add a DS3231 RTC and SD card to log readings every 15 minutes, or add WiFi to push data to ThingSpeak.

Project 2 — Solar-Powered Outdoor Weather Logger

Project Description

A fully self-powered outdoor weather station using a small solar panel and LiPo battery. The ESP32 wakes from deep sleep every 30 minutes, reads BME280 temperature/humidity/pressure and a rain sensor, logs the data to an SD card, then goes back to sleep. Total power draw while awake is under 200ms — the battery lasts indefinitely with even a 1W solar panel.

Components: ESP32, BME280, rain sensor module, SD card module, 6V 1W solar panel, TP4056 LiPo charger, 18650 LiPo battery, weatherproof enclosure.

Circuit Description

ComponentPinESP32 Pin
BME280SDA/SCLGPIO 21/22
Rain sensorAOGPIO 34
SD ModuleMOSI/MISO/SCK/CSGPIO 23/19/18/5

Connect the solar panel output to the TP4056 solar input (IN+ / IN–). The TP4056 charges the 18650 and provides 5V to the ESP32's Vin pin.

Code

#include <Adafruit_BME280.h>
#include <SD.h>
#include <RTClib.h>

Adafruit_BME280 bme;
RTC_DS3231 rtc;
#define RAIN_PIN 34
#define SD_CS     5

void setup() {
  Serial.begin(115200);
  bme.begin(0x76);
  rtc.begin();
  SD.begin(SD_CS);

  float temp  = bme.readTemperature();
  float hum   = bme.readHumidity();
  float pres  = bme.readPressure() / 100.0F;
  int   rain  = analogRead(RAIN_PIN);
  DateTime now = rtc.now();

  String row = String(now.unixtime()) + "," +
               String(now.year()) + "-" + String(now.month()) + "-" + String(now.day()) + " " +
               String(now.hour()) + ":" + String(now.minute()) + "," +
               String(temp, 1) + "," + String(hum, 1) + "," +
               String(pres, 1) + "," + String(rain);

  if (!SD.exists("/weather.csv")) {
    File f = SD.open("/weather.csv", FILE_WRITE);
    f.println("unix,datetime,temp_c,hum_pct,pres_hpa,rain_raw");
    f.close();
  }
  File f = SD.open("/weather.csv", FILE_APPEND);
  if (f) { f.println(row); f.close(); }
  Serial.println("Logged: " + row);

  // Deep sleep for 30 minutes
  esp_sleep_enable_timer_wakeup(30ULL * 60 * 1000000);
  esp_deep_sleep_start();
}

void loop() {}

Conclusion

Solar-powered deep-sleep logging is the template for every commercial IoT field sensor. Next step: add cellular (SIM800L) so the station uploads data over mobile without needing home WiFi range.

Project 3 — OpenWeatherMap Forecast Display

Project Description

Fetch a 5-day weather forecast for any city from the OpenWeatherMap API and display it on a 2.4" colour TFT screen. The display shows today's temperature, humidity, wind speed, and a weather icon (sunny, cloudy, rainy). Updates every 30 minutes. A free OpenWeatherMap API key is all you need.

Components: ESP32, 2.4" ILI9341 SPI TFT display (240×320), breadboard, jumper wires.

Circuit Description

TFT PinESP32 Pin
MOSIGPIO 23
SCKGPIO 18
CSGPIO 15
DCGPIO 2
RSTGPIO 4
VCC3.3V
GNDGND

Code

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <TFT_eSPI.h> // configure User_Setup.h for ILI9341

TFT_eSPI tft = TFT_eSPI();
const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* apiKey   = "YOUR_OPENWEATHERMAP_API_KEY";
const char* city     = "London";

void fetchWeather() {
  HTTPClient http;
  String url = String("http://api.openweathermap.org/data/2.5/weather?q=") +
               city + "&appid=" + apiKey + "&units=metric";
  http.begin(url);
  int code = http.GET();
  if (code != 200) { http.end(); return; }

  StaticJsonDocument<1024> doc;
  deserializeJson(doc, http.getString());
  http.end();

  float temp  = doc["main"]["temp"];
  float hum   = doc["main"]["humidity"];
  float wind  = doc["wind"]["speed"];
  const char* desc = doc["weather"][0]["description"];

  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_WHITE, TFT_NAVY);
  tft.setTextSize(2);
  tft.setCursor(10, 20); tft.println(city);
  tft.setTextSize(4);
  tft.setCursor(10, 60); tft.print(temp, 1); tft.println(" C");
  tft.setTextSize(1);
  tft.setCursor(10, 130); tft.println(desc);
  tft.setCursor(10, 150); tft.printf("Humidity: %.0f %%", hum);
  tft.setCursor(10, 170); tft.printf("Wind: %.1f m/s", wind);
}

void setup() {
  tft.init(); tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE);
  tft.setTextSize(2);
  tft.setCursor(20, 100); tft.println("Connecting...");

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  fetchWeather();
}

void loop() {
  delay(1800000); // refresh every 30 minutes
  fetchWeather();
}

Conclusion

Parsing a JSON REST API on a microcontroller is a core IoT skill — the same pattern works for news feeds, stock prices, sports scores, and more. Next step: show a 5-day forecast by calling the /forecast endpoint and cycling through the days on button press.

Project 4 — DIY Wind Speed Anemometer

Project Description

Build a cup anemometer using a hall-effect sensor (A3144) and a small magnet on the rotating arm. Each rotation generates a pulse; the ESP32 counts pulses per second and converts to wind speed in km/h. Results are shown on an OLED and logged to a web server.

Components: ESP32, A3144 hall-effect sensor, small neodymium magnet, 0.96" OLED, 10kΩ pull-up resistor, 3D-printed or improvised cup anemometer frame.

Circuit Description

A3144ESP32 Pin
OUTGPIO 4 (interrupt)
VCC5V (Vin)
GNDGND

Place the magnet on one of the anemometer cups; the hall-effect sensor mounts on the fixed base, 2–3mm from the magnet's path. Add a 10kΩ pull-up resistor from the OUT pin to 5V.

Code

#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define HALL_PIN 4
Adafruit_SSD1306 display(128, 64, &Wire, -1);

volatile int pulseCount = 0;
float windSpeed_kmh = 0;
unsigned long lastCalc = 0;

// Anemometer calibration: 1 rotation per second = ? km/h
// Depends on cup arm radius. For r = 9cm: circumference = 2*pi*0.09 = 0.565m
// = 0.565 * 3.6 = 2.035 km/h per rotation/sec
#define KMH_PER_RPS 2.035

void IRAM_ATTR hallISR() { pulseCount++; }

void setup() {
  Serial.begin(115200);
  pinMode(HALL_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HALL_PIN), hallISR, FALLING);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(WHITE);
}

void loop() {
  if (millis() - lastCalc >= 1000) {
    noInterrupts();
    int pulses = pulseCount; pulseCount = 0;
    interrupts();
    windSpeed_kmh = pulses * KMH_PER_RPS;
    lastCalc = millis();

    Serial.printf("Wind: %.1f km/h (%d pulses)\n", windSpeed_kmh, pulses);

    display.clearDisplay();
    display.setTextSize(1); display.setCursor(0,0); display.println("Wind Speed");
    display.setTextSize(3); display.setCursor(5, 20);
    display.printf("%.1f", windSpeed_kmh);
    display.setTextSize(1); display.setCursor(90, 30); display.println("km/h");
    display.display();
  }
}

Conclusion

Interrupt-driven pulse counting is the right technique for any rotating sensor — water flow meters, turbines, and tachometers all use the same approach. Next step: add a wind vane (potentiometer) to measure direction, and combine with the BME280 for a complete outdoor station.

Project 5 — ThingSpeak Cloud Weather Dashboard

Project Description

Push temperature, humidity, pressure, and heat index to ThingSpeak every 60 seconds. ThingSpeak automatically graphs all channels in real time, accessible from any browser or phone. Free tier supports 3 million messages per year — more than enough for a home weather station. Share your channel publicly and your friends can follow your local weather.

Components: ESP32, BME280, breadboard, jumper wires. (ThingSpeak free account required.)

Circuit Description

BME280ESP32 Pin
SDAGPIO 21
SCLGPIO 22
VCC3.3V
GNDGND

Code

#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_BME280.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_THINGSPEAK_WRITE_API_KEY";
Adafruit_BME280 bme;

float heatIndex(float t, float h) {
  // Steadman's simplified heat index
  return -8.78469475556 + 1.61139411*t + 2.33854883889*h
         - 0.14611605*t*h - 0.012308094*t*t - 0.0164248277778*h*h
         + 0.002211732*t*t*h + 0.00072546*t*h*h - 0.000003582*t*t*h*h;
}

void postThingSpeak(float t, float h, float p, float hi) {
  HTTPClient http;
  String url = "https://api.thingspeak.com/update?api_key=" + String(apiKey) +
               "&field1=" + String(t,2) +
               "&field2=" + String(h,2) +
               "&field3=" + String(p,2) +
               "&field4=" + String(hi,2);
  http.begin(url);
  int code = http.GET();
  Serial.println("ThingSpeak response: " + String(code));
  http.end();
}

void setup() {
  Serial.begin(115200);
  bme.begin(0x76);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nConnected: " + WiFi.localIP().toString());
}

void loop() {
  float t  = bme.readTemperature();
  float h  = bme.readHumidity();
  float p  = bme.readPressure() / 100.0F;
  float hi = heatIndex(t, h);
  Serial.printf("T=%.1f H=%.1f P=%.1f HI=%.1f\n", t, h, p, hi);
  postThingSpeak(t, h, p, hi);
  delay(60000); // ThingSpeak free tier minimum: 15 seconds
}

Conclusion

ThingSpeak turns raw sensor data into professional-looking charts with zero backend code. Next step: use ThingSpeak's built-in MATLAB analysis to send an email alert when temperature exceeds a threshold, or embed your public chart in a website.


More ESP32 & Arduino Project Guides

Post a Comment

0 Comments