Solar-powered ESP32 weather station that collects temperature, humidity, pressure, and GPS location every 5 minutes and publishes to GCP Pub/Sub via MQTT over TLS.
| Component | Part | Interface |
|---|---|---|
| MCU | ESP32-WROOM-32 DevKit | -- |
| Environment sensor | BME280 | I2C (addr 0x76) |
| GPS receiver | NEO-M8N | UART @ 9600 baud |
| Battery | 18650 Li-ion 3.7 V | ADC via voltage divider |
| Solar panel | 6 V panel | TP4056 charge controller |
graph LR
subgraph Solar Power
SOLAR["6 V Solar Panel"] --> TP4056["TP4056\nCharge Controller"]
TP4056 --> BAT["18650 Li-ion\n3.7 V"]
end
BAT --> |VCC / GND| ESP32["ESP32-WROOM-32"]
subgraph Voltage Divider
BAT --> R1["R1\n100 k"]
R1 --> MID((" "))
MID --> R2["R2\n100 k"]
R2 --> GND1["GND"]
end
MID --> |GPIO34 / ADC1_CH6| ESP32
subgraph BME280 Sensor
BME["BME280\nTemp / Hum / Press"]
end
ESP32 --> |GPIO21 SDA| BME
ESP32 --> |GPIO22 SCL| BME
subgraph GPS Module
GPS["NEO-M8N\nGPS Receiver"]
end
ESP32 --> |GPIO17 TX2| GPS
GPS --> |GPIO16 RX2| ESP32
graph LR
ESP32["ESP32"] -->|MQTT / TLS| HMQ["HiveMQ Cloud"]
HMQ -->|Webhook| CF["GCP Cloud\nFunction"]
CF -->|Publish| PS["Pub/Sub\nweatherprobe-readings"]
ESP32 -.->|Offline| SPIFFS["SPIFFS\nRing Buffer"]
SPIFFS -.->|Drain on reconnect| ESP32
Every 5 minutes the firmware:
- Reads BME280 (temperature, humidity, pressure) over I2C
- Reads GPS (latitude, longitude, altitude) over UART -- 30 s timeout, falls back to last-known position stored in RTC memory
- Reads battery voltage via ADC (16-sample average)
- Connects WiFi, synchronises time via SNTP
- Publishes a JSON reading to HiveMQ via MQTT/TLS (QoS 1)
- Drains up to 10 buffered readings from SPIFFS
- If publish fails, stores the reading in the SPIFFS ring buffer
- Enters deep sleep
{
"device_id": "weatherprobe-01",
"ts": 1709380800,
"temp_c": 22.5,
"humidity_pct": 45.2,
"pressure_hpa": 1013.25,
"lat": 60.1699,
"lon": 24.9384,
"alt_m": 15.3,
"battery_pct": 78,
"battery_v": 3.82,
"gps_fix": true
}ard_weatherprobe/
├── CMakeLists.txt Project-level CMake
├── Makefile Convenience wrapper for idf.py
├── sdkconfig.defaults ESP-IDF Kconfig defaults
├── partitions.csv Custom partition table (NVS + SPIFFS)
├── provision.sh Interactive credential provisioning
├── main/
│ ├── CMakeLists.txt Component source list
│ ├── main.c Entry point and wake-cycle orchestration
│ ├── config.h Pin assignments, timing, buffer limits
│ ├── sensor_bme280.c/h BME280 I2C driver with Bosch compensation
│ ├── sensor_gps.c/h NEO-M8N UART NMEA GGA parser
│ ├── battery.c/h ADC battery voltage monitor
│ ├── data_buffer.c/h SPIFFS FIFO ring buffer
│ ├── credentials.c/h NVS credential loader
│ └── wp_mqtt.c/h WiFi STA + MQTT/TLS client
└── cloud/
├── cloud_function/
│ ├── main.py GCP Cloud Function (MQTT webhook -> Pub/Sub)
│ └── requirements.txt
└── pubsub_setup.sh Create Pub/Sub topic and subscription
Requires the ESP-IDF toolchain (v5.x).
# Build
make build
# Flash and monitor (most common)
make fm
# Override serial port
make flash PORT=/dev/ttyACM0
# Provision WiFi and MQTT credentials to NVS
make provision
# Other targets: clean, fullclean, menuconfig, size, eraseOr use idf.py directly:
idf.py build
idf.py -p /dev/ttyUSB0 flash monitorBefore first use, flash WiFi and MQTT credentials into the NVS partition:
./provision.shThe script prompts for WiFi SSID/password and MQTT broker details, then writes them to the ESP32's NVS flash. Credentials never appear in source code.
- Create the Pub/Sub topic and subscription:
cd cloud && bash pubsub_setup.sh
- Deploy the Cloud Function:
cd cloud/cloud_function gcloud functions deploy weatherprobe-bridge \ --runtime python312 \ --trigger-http \ --entry-point webhook \ --set-env-vars GCP_PROJECT=<project>,WEBHOOK_SECRET=<secret>
- Configure HiveMQ Cloud to forward messages to the Cloud Function URL.
MIT