DIY Weather Station With Anemometer + Code (Arduino ESP32)

by NewsonsElectronics in Circuits > Arduino

37 Views, 2 Favorites, 0 Comments

DIY Weather Station With Anemometer + Code (Arduino ESP32)

Arduino DIY Weather Station with Anemometer + Code
cover.jpg
Xbox controller won't connect! (26).png
IMG20250524143425.jpg

🌦️ DIY Weather Station with Anemometer | Full Build Tutorial 🌬️ In this instructable, I’ll show you how to build your very own weather station at home, complete with an anemometer to measure wind speed! Whether you're a weather enthusiast, a STEM educator, or just love DIY electronics, this project is fun, educational, and super rewarding.

Supplies

part2.jpg
kit1.jpg
connections.jpg

This weather station includes the following sensors and components:

  1. Vibration Sensor
  2. DHT11 Temperature & Humidity Sensor
  3. Anemometer (Wind Speed Sensor)
  4. UV Sensor
  5. Light Sensor (LDR or Photodiode)
  6. Raindrop Sensor
  7. PM2.5 Air Quality Sensor
  8. Atmospheric Pressure Sensor (e.g., BMP280 or similar)
  9. OLED Display
  10. ESP32 Microcontroller

The full kit can be bought here on Amazon.com (https://amzn.to/45oZtQn)

*note the kit does not come with batteries 18650 Lithium batteries these can be bought here (https://amzn.to/3Sh0Bhv)

Assembly Instructions

assemble.jpg
Assembly Video: QE011 Weather Station Starter Kit

The full assembly instructions can be found in the attached pdf file.

The full build video can be found here. (https://www.youtube.com/watch?v=tOppkRcrCWg)

Coding

Screenshot_2025-05-24-14-00-48-91_40deb401b9ffe8e1df2f1cc5ba480b12.jpg

There are individual Arduino sketches provided for each sensor, but to make things simple, I recommend using the final sketch called 16atmosphere_web. This sketch connects your ESP32 to a Wi-Fi hotspot and displays all the weather data on a webpage.

✅ Before You Upload the Code:

  1. Install All Required Libraries
  2. Make sure you have installed all the necessary libraries.
  3. You can download them using the Library Manager in the Arduino IDE or from the provided website link.
  4. Install the ESP32 Board Package
  5. In the Arduino IDE, go to File > Preferences, and add the following URL to the "Additional Board Manager URLs":
bash
CopyEdit
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  1. Then go to Tools > Board > Boards Manager, search for ESP32, and install it.
  2. Edit the Wi-Fi Credentials
  3. Open the 16atmosphere_web sketch.
  4. Find the lines with const char* ssid = "YOUR_SSID"; and const char* password = "YOUR_PASSWORD";.
  5. Replace "YOUR_SSID" and "YOUR_PASSWORD" with your phone’s hotspot name and password (or your home Wi-Fi).

🚀 Upload and Run:

  1. Connect your ESP32 to your computer via USB.
  2. Select the correct board and COM port from the Tools menu.
  3. Click the Upload button.
  4. Once uploaded, open the Serial Monitor to find the local IP address assigned to your ESP32.
  5. Type that IP address into any web browser to view your weather station data live!



#include <WiFi.h>
#include <WebServer.h>
#include <ACB_Atmospheric.h>
#include <ACB_DHT11.h>
#include <ACB_WindCup.h>
#include <ACB_Ultraviolet.h>
#include <ACB_PM25.h>
#include <U8g2lib.h>
#include <Wire.h>
#include "Arduino.h"
#include "esp_http_server.h"
#include <ESP32Servo.h>

#include "soc/rtc_wdt.h"
#include "esp_task_wdt.h"

// set wifi name
#define WIFI_SSID "ACEBOTT"
// set wifi password
#define WIFI_PASSWORD "12345678"

#define WIFI_CONNECT_MAX_TIMEOUT 5 // time out of connetion
int wifiConnectRetryNum = 0; // WIFI reconnect num
int wifiConnectState = false; // WIFI connect state

ACB_Atmospheric BMP;
ACB_Ultraviolet uv;
ACB_PM25 PM25;
ACB_WindCup wind;
ACB_DHT11 dht23(23);
Servo myservo1;

int val=0;
int pm25_value;

typedef struct {
httpd_req_t *req;
size_t len;
} jpg_chunking_t;
httpd_handle_t camera_httpd = NULL;

// data of sensors
int light=0;
int raindrop=0;
int Vibration;
int Vibration1 = 1;
int hum;
int tem;
float bmp_pressure;
float bmp_temp;
float _PM25;
float wincup;
int uv_light;

WebServer server(80);

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

void startCameraServer();


void setup() { // initialize
Serial.begin(115200); // set the baud rate to 115200
myservo1.attach(4);
myservo1.write(90);
connectWifi();

startCameraServer();

BMP.Atmospheric_init();
uv.setpin(33);
PM25.setpin(17,16);
wind.setpin(32);
pinMode(36, INPUT); // set pin light sensor as input
pinMode(39, INPUT); // set pin raindrop sensor as input
pinMode(18, INPUT); // set pin vibration sensor as input
pinMode(23, INPUT); // set pin DHT11 sensor as input
u8g2.setI2CAddress(0x3C*2);
u8g2.begin();
u8g2.enableUTF8Print();

esp_task_wdt_init(10000, true);
xTaskCreatePinnedToCore(loopMotorTask, "MotorTask",20000,NULL,5,NULL,0);

}

void connectWifi()
{
WiFi.setTxPower(WIFI_POWER_19_5dBm);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println(".");
wifiConnectRetryNum++;
if (wifiConnectRetryNum > WIFI_CONNECT_MAX_TIMEOUT)
{
break;
}
}
if (WiFi.status() != WL_CONNECTED)
{
wifiConnectState = false;

}
else
{
wifiConnectState = true;
}
Serial.print("IP:");
Serial.println(WiFi.localIP());
}

void loop() {
u8g2.firstPage();
do
{
Page1();
}while(u8g2.nextPage());

xTaskCreatePinnedToCore(loopMotorTask1, "MotorTask",20000,NULL,5,NULL,0);
}

int getpm25Value()
{
pm25_value = PM25.read();
// Serial.println(pm25_value);
return pm25_value;
}

void loopMotorTask(void *pvParameters) {
for (;;) {
getpm25Value();
esp_task_wdt_reset();
}
}


void Page1() {//define content of OLED

tem = dht23.get_Temperature_Data();//get temperatur value
hum = dht23.get_Humidity_Data();//get humidity value
raindrop = analogRead(39);
if(raindrop>1000){
u8g2.setFontPosBottom();
u8g2.setFont(u8g2_font_open_iconic_all_4x_t);//rain
u8g2.drawGlyph(0,0+4*8,241);
}
else{
u8g2.setFontPosBottom();
u8g2.setFont(u8g2_font_open_iconic_all_4x_t);//sun
u8g2.drawGlyph(0,0+4*8,259);
}

if (wifiConnectState){
u8g2.setFont(u8g2_font_timR08_tf);
u8g2.setFontPosTop();
u8g2.setCursor(50,0);
u8g2.print(WiFi.localIP());//display Wifi IP
} else {
u8g2.setFont(u8g2_font_timR08_tf);
u8g2.setFontPosTop();
u8g2.setCursor(50,0);
u8g2.print("Connect WiFi Fail");//display error of wifi connection
}

u8g2.setFont(u8g2_font_timR10_tf);
u8g2.setFontPosTop();
u8g2.setCursor(50,12);
u8g2.print(String(tem)+String("°c") );//display temperature value

u8g2.setFont(u8g2_font_timR10_tf);
u8g2.setFontPosTop();
u8g2.setCursor(95,12);
u8g2.print(String(hum)+String("%") );//display humidity value
u8g2.setFont(u8g2_font_timR10_tf);
u8g2.setCursor(50,25);
u8g2.print(String("PM2.5 : ") + String(PM25.read()));//display PM2.5 value
u8g2.setFont(u8g2_font_timR08_tf);
u8g2.setCursor(0,43);
u8g2.print(String("Lt: ") + String(light));//display light value
u8g2.setCursor(0,55);
u8g2.print(String("Uv: ") + String(uv.read("level")));//display UV index

u8g2.setCursor(50,43);
u8g2.print(String("P: ") + String(int(BMP.read("Press"))));//display barometric pressure
u8g2.setCursor(50,55);
u8g2.print(String("w: ") + String(wind.read()));//display wind speed value
u8g2.setCursor(95,43);
u8g2.print(String("R: ") + String(raindrop));//display raindrop value
u8g2.setCursor(95,55);
u8g2.print(String("V: ") + String(Vibration1));//display vibration value
}

static esp_err_t data_handler(httpd_req_t *req) {
// get data of sensors
bmp_pressure = BMP.read("Press");
bmp_temp = BMP.read("Temp");
_PM25 = pm25_value;
light = analogRead(36);
tem = dht23.get_Temperature_Data();
wincup = wind.read();

uv_light = uv.read("level");
raindrop = analogRead(39);
// creat jason string
String response = "{";
response += "\"temp\":" + String(tem) + ",";
response += "\"hum\":" + String(hum) + ",";
response += "\"press\":" + String(bmp_pressure) + ",";
response += "\"alt\":" + String(_PM25) + ",";
response += "\"light\":" + String(light) + ",";
response += "\"wind\":" + String(wincup) + ",";
response += "\"vib\":" + String(Vibration1) + ",";
response += "\"uv\":" + String(uv_light) + ",";
response += "\"raindrop\":" + String(raindrop);
response += "}";

// et the response header and send the response
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, response.c_str(), response.length());
// return ESP_OK;

Vibration1 = 1;

char* buf;
size_t buf_len;
char variable[32] = {0,};
char value[32] = {0,};
char s1_value[32] = {0,};

buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = (char*)malloc(buf_len);
if(!buf){
httpd_resp_send_500(req);
return ESP_FAIL;
}
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
if ((httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK &&
httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK &&
httpd_query_key_value(buf, "s1", s1_value, sizeof(s1_value)) == ESP_OK)){
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
} else {
httpd_resp_send_404(req);
return ESP_FAIL;
}

int val = atoi(value);
int s1 = atoi(s1_value);
int res = 0;
if(!strcmp(variable, "qx")) {
if (val == 1){
myservo1.write(s1);
}

} else {
res = -1;
}

if(res){ return httpd_resp_send_500(req); }

httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, NULL, 0);
}

// get vibration value
int getvibrationValue()
{
Vibration = digitalRead(18);
if (Vibration==0){
Vibration1 = 0;
}
return Vibration1;
}

void loopMotorTask1(void *pvParameters) {
for (;;) {
// getpm25Value();
getvibrationValue();
esp_task_wdt_reset();
}
}

//*************************Web_HTML*************************
static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Station</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #ffffff;
color: #24292e;
margin: 0;
padding: 20px;
}

.container {
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

h1 {
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin: 2px 0 10px;
}

.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}

.card {
padding: 10px;
background-color: #ffffff;
border: 1px solid #eaeaea;
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 150px;
margin: 0;
}

.text-purple {
color: #6a0dad;
font-size: 1.2rem;
margin: 0;
}

.value {
font-size: 22px;
margin: 0;
}

.slider-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
}

.slider-container p {
margin-right: 10px;
color: #000;
}

input[type="range"] {
width: 200px;
}
</style>
</head>
<body>
<div>
<h1>Weather Station</h1>

<div class="grid">
<div class="card">
<span class="text-purple">Temperature</span>
<p class="value" id="temperature"></p> <!-- Temperature -->
</div>
<div class="card">
<span class="text-purple">&nbsp;&nbsp;&nbsp;Humidity&nbsp;&nbsp;&nbsp;</span>
<p class="value" id="humidity"></p> <!-- humidity -->
</div>
<div class="card">
<span class="text-purple">&nbsp;&nbsp;&nbsp;Pressure&nbsp;&nbsp;&nbsp;</span>
<p class="value" id="pressure"></p> <!-- barometric pressure -->
</div>
<div class="card">
<span class="text-purple">PM2.5</span>
<p class="value" id="altitude"></p> <!-- pm2.5 -->
</div>
<div class="card">
<span class="text-purple">Light</span>
<p class="value" id="light"></p> <!-- lightness -->
</div>
<div class="card">
<span class="text-purple">Wind</span>
<p class="value" id="wind"></p> <!-- wind speed -->
</div>
<div class="card">
<span class="text-purple">Vibration</span>
<p class="value" id="vibration"></p> <!-- vibration -->
</div>
<div class="card">
<span class="text-purple">Ultraviolet</span>
<p class="value" id="uv"></p> <!-- UV level -->
</div>
<div class="card">
<span class="text-purple">Raindrop</span>
<p class="value" id="raindrop"></p> <!-- rain -->
</div>
</div>

<div class="slider-container">
<p>Servo:</p>
<input type="range" style="width: 200px;" id="slider1" min="0" max="180" value="90" ontouchend="fetch(document.location.origin+'/control?var=car&val=3');" oninput="sendSliderValue11();" onmousedown="sendSliderValue11();" onchange="updateValue1(this.value);">
<p id="sliderValue1" style="color: black; width: 25px; margin-left: 10px;">90</p>
</div>
</div>

<script>
setInterval(() => {
fetch('/data')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
document.getElementById('temperature').innerText = data.temp + '°C';
document.getElementById('humidity').innerText = data.hum + '%';
document.getElementById('pressure').innerText = data.press + ' hPa';
document.getElementById('altitude').innerText = data.alt;
document.getElementById('light').innerText = data.light;
document.getElementById('wind').innerText = data.wind + 'm/s';
document.getElementById('vibration').innerText = data.vib;
document.getElementById('uv').innerText = data.uv;
document.getElementById('raindrop').innerText = data.raindrop;
})
.catch(error => {
console.error('Fetching data failed:', error);
});
}, 500);

function sendSliderValue11() {
var sliderValue1 = document.getElementById("slider1").value;
var url = document.location.origin + "/data?var=qx&val=1&s1=" + sliderValue1;
fetch(url);
}

function updateValue1(value) {
document.getElementById("sliderValue1").textContent = value;
}

</script>



</body>
</html>
)rawliteral";

static esp_err_t index_handler(httpd_req_t *req){
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML));
}
void startCameraServer() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};

httpd_uri_t data_uri = {
.uri = "/data",
.method = HTTP_GET,
.handler = data_handler,
.user_ctx = NULL
};

Serial.printf("Starting web server on port: '%d'\n", config.server_port);
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(camera_httpd, &index_uri);
httpd_register_uri_handler(camera_httpd, &data_uri);
} else {
Serial.println("Failed to start server!");
}
}

Future Ideas / Improvements

🔧 Suggested Improvements for the Weather Station Project

  1. Add Battery Level Monitoring
  2. Connect the battery pack's positive line to the ESP32’s analog reference pin (e.g., A0) through a voltage divider.
  3. Write a function to read the analog value and convert it to a battery percentage.
  4. Display the battery level on the OLED screen and web interface.
  5. Upgrade Weatherproofing for Outdoor Use
  6. Enclose the components in a sealed weatherproof box (such as an IP65-rated plastic case).
  7. Use rubber grommets or silicone sealant around cable entry points to prevent water ingress.
  8. Mount sensors that need exposure (e.g., rain, UV, anemometer) through shielded cutouts or on extensions outside the case.
  9. Switch to Rechargeable AAA Batteries
  10. Replace the 180050 lithium battery with standard rechargeable AAA batteries (NiMH recommended).
  11. Use a battery holder that fits 3 or 4 AAA cells to match the voltage requirements.
  12. Optionally include a small solar charging module for continuous outdoor use.
  13. Add Network Auto-Reconnect Subroutine
  14. Create a loop or function that repeatedly scans for available Wi-Fi networks if the ESP32 fails to connect initially.
  15. Example:
cpp
CopyEdit
void reconnectWiFi() {
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 10) {
Serial.print("Attempting to connect to WiFi...");
WiFi.begin(ssid, password);
delay(5000);
attempts++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Failed to connect. Retrying later...");
delay(10000); // Wait before trying again
reconnectWiFi(); // Recursive retry
}
}
  1. Call this function in setup() and periodically in the main loop if the connection is lost.