In this blog, we will be putting together a compact ‘Weather Station’ using a variety of sensors. The platform being used is an ESP32 development board with built-in Wi-Fi. This will provide communication to the sensors along with a power source. As we work through the project, we will interface various sensors to ESP32. In this process, we create the software sketch to run the weather station.
On completion of the project, the following data will be available:
- Temperature (BME680)
- Humidity (BME680)
- Barometric Pressure (BME280)
- Rain Gauge (Davis Rain Gauge)
- Wind Speed (Davis Anemometer)
- Wind Direction (Davis Anemometer)
Design
The project is broken up into many parts. We will work through connecting each sensor and developing the software as we connect each sensor to the ESP32. The software we use is based on the hookup guides for each sensor and the software library for that sensor.
BME 680
This compact sensor contains temperature, humidity, barometric pressure, and VOC gas sensing capabilities over SPI or I2C. The BME680 takes those sensors to the next step, containing a small MOX sensor. The heated metal oxide changes resistance based on the volatile organic compounds (VOC) in the air, so it can be used to detect gasses & alcohols such as Ethanol, Alcohol, and Carbon Monoxide and perform air quality measurements.
Pinout
Wire Color | ESP32 Connection |
---|---|
Vcc | 3.3V |
Gnd | Ground |
SCL | IO22 |
SDA | IO21 |
Code
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include “Adafruit_BME680.h”
#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println(F(“BME680 test”));
if (!bme.begin()) {
Serial.println(“Could not find a valid BME680 sensor, check wiring!”);
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
}
void loop() {
if (! bme.performReading()) {
Serial.println(“Failed to perform reading :(“);
return;
}
Serial.print(“Temperature = “);
Serial.print(bme.temperature);
Serial.println(” *C”);
Serial.print(“Pressure = “);
Serial.print(bme.pressure / 100.0);
Serial.println(” hPa”);
Serial.print(“Humidity = “);
Serial.print(bme.humidity);
Serial.println(” %”);
Serial.print(“Gas = “);
Serial.print(bme.gas_resistance / 1000.0);
Serial.println(” KOhms”);
Serial.print(“Approx. Altitude = “);
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(” m”);
Serial.println();
delay(2000);
}
Davis Anemometer
This includes both wind speed and wind direction sensors. Rugged components stand up to hurricane‐force winds yet are sensitive to a light breeze. Davis Anemometer includes sealed bearings for long life. Wind Speed is determined by a reed switch interrupt, and Wind direction is determined by the voltage output from the in-build 20K potentiometer.
Pinout
Wire Color | ESP32 Connection |
---|---|
Black | Wind speed IO12 |
Red | Ground |
Green | Wind direction pot IO32 |
Yellow | 3.3V |
Code
#include “TimerOne.h” // Timer Interrupt set to 2 seconds for read sensors
#include <math.h>
#define WindSensorPin (12) // The pin location of the anemometer sensor
#define WindVanePin (32) // The pin the wind vane sensor is connected to
#define VaneOffset 0; // define the anemometer offset from magnetic north
int VaneValue; // raw analog value from wind vane
int Direction; // translated 0 – 360 direction
int CalDirection; // converted value with offset applied
int LastValue; // last direction value
volatile bool IsSampleRequired; // this is set true every 2.5s. Get wind speed
volatile unsigned int TimerCount; // used to determine 2.5sec timer count
volatile unsigned long Rotations; // cup rotation counter used in interrupt routine
volatile unsigned long ContactBounceTime; // Timer to avoid contact bounce in isr
float WindSpeed; // speed miles per hour
void setup() {
LastValue = 0;
IsSampleRequired = false;
TimerCount = 0;
Rotations = 0; // Set Rotations to 0 ready for calculations
Serial.begin(9600);
pinMode(WindSensorPin, INPUT);
attachInterrupt(digitalPinToInterrupt(WindSensorPin), isr_rotation, FALLING);
Serial.println(“Davis Anemometer Test”);
Serial.println(“Speed (MPH)\tKnots\tDirection\tStrength”);
// Setup the timer interupt
Timer1.initialize(500000);// Timer interrupt every 2.5 seconds
Timer1.attachInterrupt(isr_timer);
}
void loop() {
getWindDirection();
// Only update the display if change greater than 5 degrees.
if(abs(CalDirection – LastValue) > 5) {
LastValue = CalDirection;
}
if(IsSampleRequired) {
// convert to mp/h using the formula V=P(2.25/T)
// V = P(2.25/2.5) = P * 0.9
WindSpeed = Rotations * 0.9;
Rotations = 0; // Reset count for next sample
IsSampleRequired = false;
Serial.print(WindSpeed); Serial.print(“\t\t”);
Serial.print(getKnots(WindSpeed)); Serial.print(“\t”);
Serial.print(CalDirection);
getHeading(CalDirection); Serial.print(“\t\t”);
getWindStrength(WindSpeed);
}
}
// isr handler for timer interrupt
void isr_timer() {
TimerCount++;
if(TimerCount == 6)
{
IsSampleRequired = true;
TimerCount = 0;
}
}
// This is the function that the interrupt calls to increment the rotation count
void isr_rotation() {
if((millis() – ContactBounceTime) > 15 ) { // debounce the switch contact.
Rotations++;
ContactBounceTime = millis();
}
}
// Convert MPH to Knots
float getKnots(float speed) {
return speed * 0.868976;
}
// Get Wind Direction
void getWindDirection() {
VaneValue = analogRead(WindVanePin);
Direction = map(VaneValue, 0, 1023, 0, 359);
CalDirection = Direction + VaneOffset;
if(CalDirection > 360)
CalDirection = CalDirection – 360;
if(CalDirection < 0)
CalDirection = CalDirection + 360;
}
// Converts compass direction to heading
void getHeading(int direction) {
if(direction < 22)
Serial.print(” N”);
else if (direction < 67)
Serial.print(” NE”);
else if (direction < 112)
Serial.print(” E”);
else if (direction < 157)
Serial.print(” SE”);
else if (direction < 212)
Serial.print(” S”);
else if (direction < 247)
Serial.print(” SW”);
else if (direction < 292)
Serial.print(” W”);
else if (direction < 337)
Serial.print(” NW”);
else
Serial.print(” N”);
}
Davis Rain Gauge
The Davis Instruments AeroCone Rain Collector can be used as a stand-alone custom rain station. The AeroCone rain collector reduces wind-induced errors by providing a more aerodynamic path around the collector. Its contours are designed to streamline the airflow and reduce flow distortions. The design improves rain data accuracy in high wind situations where the horizontal wind velocity can cause underreporting.
The inside of the rain collector is equally well designed, keeping intact the carefully engineered interior walls that reduce splash out during high rain conditions. The debris screen locks into place, ensuring that it remains in place during high winds and heavy rainfall events. Features tipping spoon technology that increases accuracy and precision for measuring rainfall. The accuracy is increased from ±4% to ±3%.
Pinout
Wire Color | Connection |
---|---|
Green | Ground |
Red | IO27 |
Code
const byte interruptPin = 27;
const int interval = 500;
double rain_m=0 ;
volatile int loop_count;
volatile unsigned long tiptime = millis();
void setup() {
Serial.begin(9600);
// Set up our digital pin as an interrupt
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), count, FALLING);
}
void loop() {
}
void count() {
// Grab the current ms count for common calculations
unsigned long curtime = millis();
// Make sure we don’t record bounces
if ((curtime – tiptime) < interval) {
return;
}
rain_mm += 0.2 ; // How long since the last tip?
unsigned long tipcount = curtime – tiptime;
tiptime = curtime;
// Calculate mm/hr from period between cup tips
double rainrate = 914400.0 / tipcount;
Serial.print(“Cup tip: “);
Serial.print(tipcount);
Serial.println(“ms”);
Serial.print(“Rain rate: “);
Serial.print(rainrate);
Serial.println(“mm/hr”);
}
Connections to ESP32
Final code for ESP32 weather station
You can find the final code for the ESP32 weather station in the following link:
https://github.com/shafeekashraf44/Weather.git
Ambee’s weather data
Ambee’s weather data is a result of the combination of vigorous research, multiple algorithms, and numerous rounds of scientific validations. Ambee’s data harnesses the power of on-ground sensors, remote satellite imagery, and a global proprietary sensor network to get credible data from multiple sources. Ambee’s accurate weather data gives businesses and individuals the power to take weather considerations into every decision.