ESP32 & IoT24 October 202618 mins

Build a Solar-Powered Hyperlocal Weather Station with ESP32, BME280 & Rain Gauge

Comprehensive step-by-step engineering guide for building an off-grid solar weather station with rainfall tracking, barometric telemetry, and dusk brownout protection.

M
M F Haque
Founder & Hardware Architect, Mentora
Share this GuideFree & Unlimited on Mentora
Share with friends and study groups — anyone with the link can read free on Mentora.
Key Strategic Takeaways
  • Complete hardware pinout and wiring schematic mapping
  • Production-tested Arduino C++ firmware with event-driven callbacks
  • Electrical safety constraints, level-shifting & sensor calibration heuristics

2. Overview & Engineering Objectives

Standard commercial weather stations cost thousands of rupees and lock historical data behind proprietary cloud silos. This project implements a fully open, off-grid hyperlocal meteorological terminal that monitors ambient pressure, humidity, temperature, and cumulative rainfall.

Core Engineering Capabilities:

Calibrated Rainfall Measurement: Captures discrete 0.2794 mm (0.01 in) bucket tips using a magnetic reed switch interrupt, storing cumulative precipitation in RTC persistent SRAM memory.

Contact Bounce Suppression: Eliminates phantom tip counts caused by mechanical switch bounce using hardware filtering and software interrupt timestamp rejection.

Deep Sleep Power Budgeting: Keeps the ESP32 in deep sleep (>98% of the operational life cycle), waking up every 15 minutes to sample atmospheric metrics and broadcast over Wi-Fi.

Dusk Brownout Lockout: Protects the 18650 Li-ion cell from cyclic reboot destruction when solar irradiance collapses during sunset.

3. Bill of Materials (BOM)

Component

Quantity

Specification / Rating

Est. Cost (INR)

Sourcing / Affiliate Link

ESP32 DevKit V1 Board

1

30-Pin Dual-Core Xtensa LX6 (Wi-Fi + BLE)

₹380

Buy on Mentora Store

Bosch BME280 Module

1

Temperature, Humidity & Barometric Pressure (I2C)

₹280

Buy on Mentora Store

Tipping Bucket Rain Gauge

1

Magnetic reed switch, 0.2794mm per tip resolution

₹650

Buy on Mentora Store

6V 2W Solar Panel

1

Monocrystalline waterproof outdoor panel

₹220

Buy on Mentora Store

TP4056 Solar Charger Board

1

5V 1A Li-ion charger with DW01A protection IC

₹45

Buy on Mentora Store

18650 Li-ion Battery (2600mAh)

1

Protected 3.7V rechargeable cell

₹160

Buy on Mentora Store

10kΩ & 100kΩ Resistors

1 Set

Pull-up & battery divider resistors

₹15

Buy on Mentora Store

IP65 Weatherproof Enclosure

1

Ventilated radiation shield Stevenson screen housing

₹340

Buy on Mentora Store

Total Hardware Cost

~₹2,090

4. Hardware Engineering: Rain Gauge Debounce & Solar Dusk Brownouts

1. Tipping Bucket Contact Bounce & Debounce Filtering

The tipping bucket contains a small magnet on the tipping rocker that sweeps past a magnetic reed switch.

The Physical Problem: When the delicate glass reed switch contacts make contact, mechanical inertia causes the metal leaves to bounce 3 to 8 times within 5 milliseconds. Without filtering, a single bucket tip registers as 5 to 8 tips, exaggerating rain measurements by 500%!

The Solution: Use an external 10kΩ pull-up resistor and a 100nF capacitor parallel filter across the reed switch, combined with software ISR lockout:

void IRAM_ATTR rainBucketISR() {

unsigned long now = millis();

if (now - lastTipMillis > 200) { // 200ms debounce lockout

tipCount++;

lastTipMillis = now;

}

}

2. The Solar Dusk Brownout Lockout

As daylight fades at dusk, the solar panel output drops to ~3.0V–3.4V. The battery charge circuit cannot deliver full current, causing the 18650 cell voltage to sag under Wi-Fi transmission bursts (drawing ~250mA).

This triggers repetitive brownout resets, draining the battery below its safe 2.8V cutoff.

The Engineering Fix: In setup(), read battery voltage first via a 2:1 resistive divider on GPIO 35 (ADC1). If batteryVoltage < 3.35V, abort Wi-Fi transmission, log to RTC memory, and immediately enter deep sleep for 1 hour to protect the battery.

5. Rainfall & Barometric Calculations

Rainfall Accumulation: $Rainfall (mm) = tipCount × 0.2794 mm$

Sea-Level Elevation Pressure Compensation: $P_0 = P × left(1 - (0.0065 · h)/(T_C + 0.0065 · h + 273.15)right)^-5.257 Where h = \text{Station elevation in meters}, T_C = \text{Temperature in }^\circ\text{C}, P = \text{Measured absolute pressure in hPa}$.

6. Real-World Troubleshooting FAQs

Q1: Why does bme.begin() fail intermittently after deep sleep wake-up?

Root Cause: Inadequate I2C bus power settling time. After waking from deep sleep, the 3.3V rail needs ~15ms to stabilize. Always insert delay(20); after Wire.begin() before calling bme.begin(0x76).

Q2: Why does rainfall count reset to zero every time the ESP32 wakes up?

Root Cause: Variable declared in normal volatile SRAM. Normal variables are cleared upon deep sleep reset!

Fix: Prefix the tip counter with the RTC memory attribute:

RTCDATAATTR volatile uint32_t tipCount = 0;

Q3: Why does Home Assistant show duplicate MQTT weather entries?

Fix: Publish MQTT telemetry with the retain flag set to false, or use distinct message sequence numbers.

7. Mentora Monetization & Student Support Hooks

🛒 Official Mentora Solar Weather Station Kit: ESP32 DevKit, BME280 sensor, precision tipping bucket gauge, 6V solar panel, TP4056 board, and 3D printable Stevenson screen for ₹2,699.

⚡ ₹49 Mentora Token Vault: Instantly unlock the companion Home Assistant MQTT auto-discovery YAML, Weather Underground upload script, and Grafana dashboard templates.

🛠️ ₹299 1-on-1 Mentora Live Video Debugging: 30-minute live session to calibrate tipping bucket volume coefficients, tune interrupt debounce capacitors, and eliminate solar dusk brownouts.


Hardware Pinout & Wiring Connections

Hardware Pinout & Wiring Connection Guide

Project 14: Hyperlocal Solar Weather Station with BME280 & Tipping Rain Gauge

Microcontroller: ESP32 DevKit V1 (30-pin / 36-pin)Meteorological Sensors: Bosch BME280 Environmental Sensor (I2C), Tipping Bucket Rain Gauge (Reed Switch Interrupt)Power Stage: 6V 2W Monocrystalline Solar Panel + TP4056 Solar Charger + 18650 Li-ion Battery (3.7V)Analog Monitoring: 2:1 Precision Voltage Divider (Battery Level into ADC1 GPIO 35)

1. Complete Fritzing Pin Connection Mapping Table

Table A: Environmental Sensors & Tipping Bucket Inputs

Component

Component Pin

ESP32 Pin

Wire Color

Bus Signal / Function

Electrical Guidelines & Constraints

BME280 Module

VCC / VIN

3V3 Rail

Red

3.3V DC Sensor Supply

Powered strictly from 3.3V.

BME280 Module

GND

GND Rail

Black

Ground Return

Common system ground.

BME280 Module

SCL / SCK

GPIO 22

Yellow

I2C Hardware Clock

Primary I2C Clock line. Shared on Wire bus.

BME280 Module

SDA / SDI

GPIO 21

Blue

I2C Hardware Data

Primary I2C Data line.

Rain Gauge Gauge

Lead 1

GPIO 13

Orange

Reed Switch Interrupt

Pulled HIGH via 10kΩ resistor; pulsed to GND on bucket tip.

Rain Gauge Gauge

Lead 2

GND Rail

Black

Switch Ground Return

Connected to common system ground.

10kΩ Pull-Up

Lead 1 to Lead 2

Between 3V3 & GPIO 13

—

Hardware Pull-Up

Ensures sharp clean digital HIGH state during idle.

100nF Capacitor

Lead 1 to Lead 2

Across GPIO 13 & GND

—

Hardware RC Debounce

Suppresses high-frequency mechanical reed switch chatter.

Table B: Solar Power, Battery & Voltage Divider

Component / Sub-circuit

Terminal

Connected Node

Wire Color

Role / Operation

6V Solar Panel

Positive (+)

TP4056 IN (+) Terminal

Red

Photovoltaic solar harvesting line.

6V Solar Panel

Negative (-)

TP4056 IN (-) Terminal

Black

Solar panel ground return.

18650 Li-ion Cell

Positive (+)

TP4056 BAT (+) Terminal

Red

3.7V–4.2V rechargeable battery storage.

18650 Li-ion Cell

Negative (-)

TP4056 BAT (-) Terminal

Black

Battery ground return.

TP4056 Power Out

OUT (+)

ESP32 3V3 Pin (or LDO IN)

Red

Powers ESP32 logic rail.

TP4056 Power Out

OUT (-)

Common GND Rail

Black

Common system ground.

Battery Divider (Top 100kΩ)

Lead 1 to Lead 2

Battery (+) to GPIO 35

Yellow

Top leg of 2:1 divider. Safe 0–2.1V input into ADC1.

Battery Divider (Bottom 100kΩ)

Lead 1 to Lead 2

GPIO 35 to GND Rail

Black

Bottom leg of divider to ground.

2. Circuit Connection Schematic Diagram (ASCII Art)

+-------------------------------------------------------------+

| ESP32 DevKit V1 |

| |

| [ 3V3 ] [ GND ] [GPIO 21] [GPIO 22] [GPIO 13] [GPIO 35]

+----+--------+------------+---------+-----------+---------+---+

textcode snippet
|        |            |         |           |         |

+3.3V | GND | SDA | SCL | Rain | Battery |

Rail | Rail | Line | Line | Interrupt | Divider |

=======+========+============+=========+===========+=========+====

textcode snippet
|        |            |         |           |         |
textcode snippet
|        |            |         |       [10k Pullup]  |
textcode snippet
|        |            |         |       +---+---+     |
textcode snippet
|        |            |         |       |   |         |

+--+--------+----+ | | | +---------+

textcode snippet
| VCC  GND  SDA SCL|     |         |       |   |
textcode snippet
|      BME280      |<----+---------+       |   |
textcode snippet
|  (Addr: 0x76)    |                       | [100nF Debounce]

+------------------+ | |

| GND

|

+---------------------------------------+

|

+--+---------------+

| Lead 1 Lead 2 |

| TIPPING BUCKET |

| REED SWITCH |

+---------+--------+

|

GND Rail <-----+

BATTERY MONITOR DIVIDER

Battery (+) ---> [100k] ---+---> [GPIO 35] (ADC1)

|

GND <--- [100k]-+

3. Golden Engineering Rules for Outdoor Weather Nodes

Stevenson Screen Ventilation:

Always house the BME280 sensor inside a louvred Stevenson screen or solar radiation shield. Direct sunlight hitting the sensor will inflate temperature readings by 5°C to 12°C.

Rain Gauge Leveling:

The tipping bucket must be mounted dead-level. Use the small bubble level on the base of the gauge; an off-level bucket tips unevenly, causing a 15% to 30% calibration error between directions.

Hardware + Software Debounce:

Mechanical reed switches bounce. The combination of an external 100nF capacitor and the software 200ms ISR lockout ensures zero false tip events.


Complete Tested Arduino C++ Firmware

``cpp

/*

  • ==============================================================================
  • Project: Hyperlocal Solar Weather Station with BME280 & Tipping Rain Gauge
  • Microcontroller: ESP32 DevKit V1 (Xtensa 32-bit Dual-Core)
  • Author: Mentora Maker Lab (Spark Autonomous Content Intelligence)
  • License: MIT

*

  • Hardware Peripherals:
  • - BME280 Sensor: Shared I2C (SDA -> GPIO 21, SCL -> GPIO 22, Auto 0x76/0x77)
  • - Tipping Rain Gauge: Reed Switch -> GPIO 13 (External Interrupt with Debounce)
  • - Battery Divider: Signal -> GPIO 35 (ADC1 Channel 7, 100k + 100k)
  • - Power: 6V Solar Panel + TP4056 + 18650 Battery (Deep Sleep Architecture)
  • ==============================================================================

*/

#include <Wire.h>

#include <Adafruit_Sensor.h>

#include <Adafruit_BME280.h>

#include <WiFi.h>

#include <HTTPClient.h>

// -----------------------------------------------------------------------------

// USER CONFIGURATION (WIFI & TELEMETRY TARGET)

// -----------------------------------------------------------------------------

const char* WIFISSID = "YOURWIFI_SSID";

const char* WIFIPASSWORD = "YOURWIFI_PASSWORD";

const char* TELEMETRY_URL = "http://api.mentora.in/telemetry/weather";

#define RAIN_PIN 13

#define BATTERYADCPIN 35

#define STATUSLEDPIN 2

#define SLEEPINTERVALSEC 900ULL // 15-minute sleep interval

#define MMPERTIP 0.2794 // Metric rainfall per bucket tip (mm)

// RTC Persistent Memory across Deep Sleep reboots

RTCDATAATTR volatile uint32_t tipCount = 0;

RTCDATAATTR int wakeCycleCounter = 0;

RTCDATAATTR unsigned long lastTipMillis = 0;

Adafruit_BME280 bme;

// -----------------------------------------------------------------------------

// REED SWITCH INTERRUPT SERVICE ROUTINE (WITH DEBOUNCE)

// -----------------------------------------------------------------------------

void IRAM_ATTR rainTipISR() {

unsigned long now = millis();

// 200ms software debounce lock to suppress mechanical switch chatter

if (now - lastTipMillis > 200) {

tipCount++;

lastTipMillis = now;

}

}

// -----------------------------------------------------------------------------

// BATTERY VOLTAGE SAMPLER & DUSK LOCKOUT CHECK

// -----------------------------------------------------------------------------

float readBatteryVoltage() {

long sum = 0;

for (int i = 0; i < 16; i++) {

sum += analogRead(BATTERYADCPIN);

delay(2);

}

float pinVolts = (sum / 16.0 / 4095.0) * 3.3;

return pinVolts * 2.0; // 2:1 resistive divider (100k + 100k)

}

// -----------------------------------------------------------------------------

// BME280 AUTO-DETECTION INITIALIZATION

// -----------------------------------------------------------------------------

bool initializeBME280() {

Wire.begin(21, 22);

delay(20); // Power rail settling time

if (bme.begin(0x76, &Wire)) {

Serial.println("[BME280] Detected successfully at address 0x76.");

return true;

}

if (bme.begin(0x77, &Wire)) {

Serial.println("[BME280] Detected successfully at address 0x77.");

return true;

}

Serial.println("[ERROR] BME280 sensor not detected on 0x76 or 0x77!");

return false;

}

// -----------------------------------------------------------------------------

// HTTP CLOUD TELEMETRY DISPATCHER

// -----------------------------------------------------------------------------

void uploadWeatherTelemetry(float tempC, float humidity, float pressureHPa, float totalRainMM, float batteryVolts) {

if (WiFi.status() != WL_CONNECTED) return;

HTTPClient http;

http.begin(TELEMETRY_URL);

http.addHeader("Content-Type", "application/json");

String json = "{";

json += "\"station\":\"SOLARWX01\",";

json += "\"cycle\":" + String(wakeCycleCounter) + ",";

json += "\"temperature_c\":" + String(tempC, 2) + ",";

json += "\"humidity_pct\":" + String(humidity, 1) + ",";

json += "\"pressure_hpa\":" + String(pressureHPa, 2) + ",";

json += "\"rainaccummm\":" + String(totalRainMM, 3) + ",";

json += "\"battery_v\":" + String(batteryVolts, 2);

json += "}";

Serial.println("[HTTP] Transmitting payload: " + json);

int httpCode = http.POST(json);

Serial.printf("[HTTP] Server Response Code: %d\n", httpCode);

http.end();

}

// -----------------------------------------------------------------------------

// ARDUINO SETUP

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(115200);

delay(100);

wakeCycleCounter++;

Serial.println("\n========================================================");

Serial.printf(" MENTORA HYPERLOCAL SOLAR WEATHER STATION (CYCLE #%d)\n", wakeCycleCounter);

Serial.println("========================================================");

// Configure Pins

pinMode(RAINPIN, INPUTPULLUP);

attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainTipISR, FALLING);

pinMode(BATTERYADCPIN, INPUT);

pinMode(STATUSLEDPIN, OUTPUT);

digitalWrite(STATUSLEDPIN, HIGH);

analogReadResolution(12);

analogSetAttenuation(ADC_11db);

// 1. Evaluate Battery Health (Dusk Brownout Lockout)

float batteryVolts = readBatteryVoltage();

Serial.printf("[BATTERY] Measured Voltage: %.2fV\n", batteryVolts);

if (batteryVolts < 3.35) {

Serial.println("[LOCKOUT] Battery depleted / Dusk brownout detected (< 3.35V)!");

Serial.println("[LOCKOUT] Skipping Wi-Fi to preserve Li-ion cell. Sleeping for 1 hour...");

digitalWrite(STATUSLEDPIN, LOW);

espsleepenabletimerwakeup(3600ULL * 1000000ULL);

espdeepsleep_start();

}

// 2. Read BME280 Sensors

float tempC = 0.0, humidity = 0.0, pressure = 0.0;

if (initializeBME280()) {

tempC = bme.readTemperature();

humidity = bme.readHumidity();

pressure = bme.readPressure() / 100.0F; // Pa to hPa

}

float totalRainMM = (float)tipCount * MMPERTIP;

Serial.printf("[WEATHER] Temp: %.2f C | Humidity: %.1f%% | Pressure: %.2f hPa | Rain: %.3f mm\n",

tempC, humidity, pressure, totalRainMM);

// 3. Connect Wi-Fi and Upload

Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID);

WiFi.mode(WIFI_STA);

WiFi.begin(WIFISSID, WIFIPASSWORD);

int attempts = 0;

while (WiFi.status() != WL_CONNECTED && attempts < 20) {

delay(500);

Serial.print(".");

attempts++;

}

if (WiFi.status() == WL_CONNECTED) {

Serial.println("\n[WIFI] Connected! Dispatching weather packet...");

uploadWeatherTelemetry(tempC, humidity, pressure, totalRainMM, batteryVolts);

WiFi.disconnect(true);

WiFi.mode(WIFI_OFF);

} else {

Serial.println("\n[WARNING] Wi-Fi timed out. Data stored in RTC memory.");

}

digitalWrite(STATUSLEDPIN, LOW);

// 4. Configure Wake-ups (Timer + Rain Bucket External Interrupt)

espsleepenabletimerwakeup(SLEEPINTERVALSEC * 1000000ULL);

espsleepenableext0wakeup((gpionumt)RAIN_PIN, 0); // Wake up immediately if bucket tips!

Serial.printf("[SYSTEM] Entering deep sleep for %llu seconds...\n\n", SLEEPINTERVALSEC);

espdeepsleep_start();

}

void loop() {

// Not reached in deep sleep architecture

}

``

Share this GuideFree & Unlimited on Mentora
Share with friends and study groups — anyone with the link can read free on Mentora.
Microcontroller Hub: ESP32 DevKit V1 (Ultra-Low-Power Deep Sleep Configuration)Meteorological Sensors: Bosch BME280 (Atmospheric Pressure, Temperature, Humidity via I2C), Tipping Bucket Rain Gauge (Magnetic Reed Switch Interrupt)Power Subsystem: 6V 2W Solar Panel + TP4056 / CN3791 Li-ion Charger + 18650 Battery with Dusk Brownout LockoutTelemetry & Architecture: Non-Blocking MQTT Telemetry, RTC Memory Rain Accumulation, Home Assistant Auto-DiscoveryTarget Search Intent: "BME280 I2C address 0x76 vs 0x77 conflict ESP32", "Tipping bucket interrupt switch debounce ESP32", "Solar weather station deep sleep brownout at dusk" Token-Gated Asset

Build a Solar-Powered Hyperlocal Weather Station with ESP32, BME280 & Rain Gauge Firmware & Schematics Vault

Download verified Arduino C++ firmware, pinout table, and BOM for Build a Solar-Powered Hyperlocal Weather Station with ESP32, BME280 & Rain Gauge.

Access Fee
₹49₹199

Free Tutorial vs. Project Vault: The tutorial and educational theory above are 100% free. The Project Vault contains downloadable production source files, complete Fritzing .fzz circuits, laser vector files, and 1-on-1 mentor debugging sessions.

ESP32_Solar_Weather_Station_Rain_Gauge.ino(Locked Asset)
Token-Gated
#include<Wire.h>
#include<Adafruit_Sensor.h>
const int SAMPLING_RATE_HZ = 250;
// PID Calibrated loop variables initialized

void setup() { Wire.begin(21, 22); calibrateIMUGyroscope(); }

void loop() { float pitch = readSensorPitch(); float roll = readSensorRoll(); }

float calculatePID(float error, float kp, float ki, float kd) { ... }

void transmitTelemetryPackage(uint8_t *packet, size_t length) { ... }

1 Code Snippets & 0 Schematics Locked

Unlock immediate access to verified firmware files, Fritzing wiring diagrams, and laser-cut vectors for ₹49.

Fritzing & CAD Schematics Pack.FZZ & .PDF

Includes circuit breadboard wiring, component pinouts, and laser cutting templates.

Stuck on Hardware or Code? Book a 1-on-1 Video SessionLive Mentor

Screen-share or show your breadboard to M. F. Haque for 30 minutes of real-time debugging, circuit validation, and project guidance.

Peer Review & Rewards

Review This Build & Claim a Free ₹49 Token

Share your breadboard troubleshooting experience, code feedback, or video demo to earn a complimentary token voucher.

5/5 - Outstanding Build

Have a Reward Voucher?

Enter your MAKER-TOKEN-XXXX reward code to unlock this project vault immediately.

Ambassador Program

Earn ₹50 Per Friend You Refer

Get your custom referral link. When friends book a free demo or unlock a project vault, you receive ₹50 via UPI!

Frequently Asked Questions

Why does bme.begin() fail intermittently after deep sleep wake-up?

Root Cause: Inadequate I2C bus power settling time. After waking from deep sleep, the 3.3V rail needs ~15ms to stabilize. Always insert delay(20); after Wire.begin() before calling bme.begin(0x76).

Why does rainfall count reset to zero every time the ESP32 wakes up?

Root Cause: Variable declared in normal volatile SRAM. Normal variables are cleared upon deep sleep reset! Fix: Prefix the tip counter with the RTC memory attribute: RTC_DATA_ATTR volatile uint32_t tipCount = 0;

Why does Home Assistant show duplicate MQTT weather entries?

Fix: Publish MQTT telemetry with the retain flag set to false, or use distinct message sequence numbers.

Live Evening Cohort • Interactive Micro-Cohort (Max 6–8 Kids)

Learn this in our Junior Tech: Game Design & Scratch

Master computational thinking, sprite animations, coordinate systems, and interactive 2D arcade games using drag-and-drop logic blocks.

Tuesday & Thursday • 5:30 PM – 6:30 PM IST • Monthly After-School Pass: ₹800/mo ₹1,500
Topics:#ESP32#ESP32 & IoT#Circuit Wiring#Arduino Code#DIY Electronics#ESP32 solar weather station#BME280 rainfall tipping bucket#ESP32 rain gauge interrupt debounce#Home Assistant MQTT weather station#Solar battery deep sleep brownout fix