Skip to Content

DFRobot Weather Station Tutorial: Anemometer, Wind Vane & Rain Gauge

DFRobot Weather Station Tutorial: Anemometer, Wind Vane & Rain Gauge

The DFRobot Weather Station Kit includes an anemometer, a wind vane, a rain bucket, and a temperature, humidity and air pressure sensor. In this tutorial you will learn how to assemble the Weather Station and connect it to an Arduino and ESP32 to collect weather data.

We will also implement a webserver that shows current and historic weather data on a website. Above is a screenshot of the website.

Let’s get started!

Required Parts

You will need the Weather Station Kit and a microcontroller. I will show you how to connect the Weather Station Kit to an Arduino and an ESP32 but for most of the tutorial I will use an ESP32-C3 SuperMini. Any other ESP32 with a 5V output will work as well but for the webserver we will want a microcontroller with built-in Wi-Fi.

You can get the ESP32-C3 SuperMini Board at AliExpress or a very similar board at Amazon, where it is called “ESP32-C3 Mini”. I linked both of them below. You also will need a USB-C cable and some Dupont wires.

Weather Station Kit

ESP32-C3 SuperMini

ESP32-C3 Mini

USB C Cable

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Features of the DFRobot Weather Station Kit

The DFRobot Weather Station Kit is a outdoor sensor package designed for weather monitoring applications. The kit combines an anemometer, a wind vane, and a tipping-bucket rain gauge.

These sensors connect to a central interface board that processes the sensor signals and provides a serial data output. The central interface board also contains additional sensors for temperature, humidity and barometric pressure.

The system is designed for use with Arduino, ESP32, and other microcontroller platforms that can be connected via the serial interface to the central interface board. The photo below shows the parts of the kit:

Parts of the DFRobot Weather Station Kit
Parts of the DFRobot Weather Station Kit (source)

Sensor System Architecture

The weather station consists of three separate meteorological sensors and an interface board with additional sensors. The board collects the sensor data and converts it into a formatted serial data stream.

Unlike many low-cost weather sensors that require individual GPIO pins and interrupt handling, this kit performs the signal processing on the interface board. The microcontroller only needs to read the serial output. This simplifies integration and reduces software complexity.

Wind Speed

The anemometer measures wind speed using a three-cup rotor assembly. Wind force causes the cups to rotate around a vertical axis. The rotation speed is proportional to the wind speed.

Wind Speed Sensor
Wind Speed Sensor

Internally, the sensor generates electrical pulses as the rotor turns. The interface board counts these pulses and converts them into wind speed measurements. The weather station reports both the average wind speed over one minute and the highest wind speed recorded during the previous five minutes. The values are transmitted through the serial interface.

Wind Direction

The wind vane measures wind direction. A tail fin aligns the vane with the airflow, causing the sensor to rotate until it points into the wind.

Wind Direction Sensor
Wind Direction Sensor

The position of the vane is converted into an angular measurement by the internal sensing mechanism. The interface board reports the wind direction as an angle in degrees. The output range covers the full 360° circle, allowing precise determination of wind direction.

Rain Gauge

Rainfall is measured using a tipping-bucket mechanism. Rainwater is collected by a funnel and directed into a small bucket assembly.

When a predefined amount of water accumulates, the bucket tips and empties itself. This tipping action generates an electrical pulse. Each pulse corresponds to a fixed quantity of rainfall. The interface board counts these pulses and calculates precipitation values.

The weather station reports rainfall accumulated during the previous hour and rainfall accumulated during the previous 24 hours.

The tipping-bucket design provides reliable long-term operation and is commonly used in automatic weather stations. The mechanism requires minimal maintenance apart from occasional cleaning of the funnel and drainage path. I recommend you mount a grid on top of the bucket to avoid it being easily blocked by leaves.

Interface Board

Note that the interface board is not sealed and not waterproof. It needs to placed in a watertight container or mounted indoors to be safe from outdoor conditions. The cables from the weather sensors (Wind, Rain) are long and the interface board can be located away from the mast with the sensors.

Interface board for Weather Station

Serial Communication Interface

A major feature of the weather station kit is its serial communication interface. The interface board continuously transmits weather data as an ASCII text string. A complete data packet is sent once per second.

The transmitted data contains wind direction, average wind speed, gust speed, temperature, rainfall statistics, humidity, and air pressure values. Each parameter is encoded with a dedicated identifier, making the data easy to parse in software.

Interface Board Jumpers

The interface board has two yellow configuration jumpers labeled J1 and J2. See the photo below:

Configuration jumpers on interface board
Configuration jumpers on interface board

These jumpers allow you to modify how the board processes and transmits data.

Jumper J1: Metric and Imperial Unit Selection

The primary function of the J1 jumper is to select the system of units for the weather data output. J1 is designated for “Metric & Imperial Jumpers”. This means that by placing a jumper cap on the appropriate pins of J1, you can configure the output data string to use either metric or imperial units for measurements like wind speed and rainfall.

Jumper J2: Baud Rate Selection for Serial Communication

The J2 jumper is responsible for switching the baud rate of the serial communication port between 2400 and 9600 bps.

A critical configuration note exists for the standard sample code provided by DFRobot. The code is written to use a default baud rate of 9600. If you are using this code directly, you must follow a specific procedure: unplug the J2 jumper cap and install the J1 jumper cap.

This indicates that the default hardware setting for the sample code is achieved by having the J1 jumper installed and the J2 jumper removed. Therefore, to use the board at 9600 baud, the jumper must be placed on J1.

In summary, the two jumpers provide a hardware-based method for two key settings:

  • J1 toggles between metric and imperial units.
  • J2 toggles between 2400 bps and 9600 bps for the serial interface.

Additional Environmental Sensors

The interface board includes a connector to a temperature, humidity, and barometric pressure sensor (BMP085) that is also part of the kit. The photo below shows the interface board with the connected sensor:

Temperature, humidity, and barometric pressure sensor connected to interface board
Temperature, humidity, and barometric pressure sensor connected to interface board

According to the current specifications, the system supports temperature measurements from -40 °C to 125 °C and relative humidity measurements from 0 % to 100 %. Air pressure data is transmitted with a resolution of 0.1 hPa.

Electrical Characteristics

The weather station needs a 5V power supply. However, note that the serial interface operates with 3.3V logic and is therefore save to use with ESP32 or Arduino boards. For details see the schematics below:

Schematics for DFRobot Weather Station Kit
Schematics of the DFRobot Weather Station Kit (source)

Assembling the DFRobot Weather Station Kit

Assembling the DFRobot Weather Station Kit is easy. The parts fit in place and can’t be assembled wrongly as long as you watch for the correct fit. You just have to screw them together.

But if you need more detailed help watch the How To Assemble DF Robot ioT Weather Station Kit video. The picture below shows the assembled Weather Station Kit without the interface board connected:

Assembled Weather Station Kit
Assembled Weather Station Kit

There is one part of the assembly that might be confusing. The interface board has four input ports but only two of them are used. One to connect the rain sensor and the other to connect the wind sensors:

Interface board with ports for rain and wind sensors
Interface board with ports for rain and wind sensors

However, there are two wind sensors – one for direction the other one for speed – and with the rain sensor you then have three cables!

What you need to do is to connect the cable from the wind speed sensor (the one with the three spoons/blades) to the wind direction sensor. See the wiring highlighted in orange in the photo below:

Connecting wind speed sensor to wind direction sensor
Connecting wind speed sensor to wind direction sensor

You then have one thicker cable from the wind direction sensor, which transport wind speed and wind direction data and one thinner cable from the rain sensor. These two cable you can now connect to the interface board as shown below:

Connecting wind and rain sensor cables to interface board
Connecting wind and rain sensor cables to interface board

If you look closely at the board, you will notice that one port is labelled “RAIN” and the other “WD/WS” for Wind Direction and Wind Speed:

Rain and Wd/WS ports on interface board
Rain and Wd/WS ports on interface board

Make sure that you plug-in the correct sensor cable to its dedicated port!

Connecting the Weather Station Kit to Arduino

Connecting the Weather Station to an Arduino is easy. Wire the four pins of the interface board (5V, GND, TX, RX) to the pins of the Arduino UNO (5V, GND, TX, RX) as shown below:

Connecting the Weather Station Kit to Arduino
Connecting the Weather Station Kit to Arduino

Just make sure that the RX and RX wires are crossed. In other words TX need to be connected to RX, and RX need to be connected to TX. The following table list the connections:

Interface BoardArduino UnoDescription
5V5VSupplies power to the interface board.
GGNDCommon ground connection between both devices.
TXRX (Pin 0)Data transmitted by the weather station is received by the Arduino.
RXTX (Pin 1)Data transmitted by the Arduino is received by the weather station.

Arduino Code: Read data from Weather Station

The following original code example from the DFRobot Wiki shows you how to read weather data from the weather station.

Note that you will need to unplug the cable on the TX&RX interface, or it will interfere with the sketch uploading. Also the code uses 9600 as the default serial port baud rate. When you upload the sample code, you will need to unplug the J2 jumper cap and install the J1 jumper cap.

/*!
 * @file  SEN0186.ino
 * @brief DFRobot brings you this new version of the weather station kit that integrates anemometer, wind vane and rain gauge.
 * @copyright  Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @license  The MIT License (MIT)
 * @author  DFRobot
 * @version  V1.0
 * @date  2023-08-03
 */

char                 databuffer[35];
double               temp;

void getBuffer()                                                                    //Get weather status data
{
  int index;
  for (index = 0; index < 35; index++) {
    if (Serial.available()) {
      databuffer[index] = Serial.read();
      if (databuffer[0] != 'c') {
        index = -1;
      }
    } else {
      index--;
    }
  }
}

int transCharToInt(char* _buffer, int _start, int _stop)                             //char to int)
{
  int _index;
  int result = 0;
  int num = _stop - _start + 1;
  int _temp[num];
  for (_index = _start; _index <= _stop; _index++) {
    _temp[_index - _start] = _buffer[_index] - '0';
    result = 10 * result + _temp[_index - _start];
  }
  return result;
}

int transCharToInt_T(char* _buffer)
{
  int result = 0;
  if (_buffer[13] == '-') {
    result = 0 - (((_buffer[14] - '0') * 10) + (_buffer[15] - '0'));
  } else {
    result = ((_buffer[13] - '0') * 100) + ((_buffer[14] - '0') * 10) + ((_buffer[15] - '0'));
  }
  return result;
}

int WindDirection()                                                                  //Wind Direction
{
  return transCharToInt(databuffer, 1, 3);
}

float WindSpeedAverage()                                                             //air Speed (1 minute)
{
  temp = 0.44704 * transCharToInt(databuffer, 5, 7);
  return temp;
}

float WindSpeedMax()                                                                 //Max air speed (5 minutes)
{
  temp = 0.44704 * transCharToInt(databuffer, 9, 11);
  return temp;
}

float Temperature()                                                                  //Temperature ("C")
{
  temp = (transCharToInt_T(databuffer) - 32.00) * 5.00 / 9.00;
  return temp;
}

float RainfallOneHour()                                                              //Rainfall (1 hour)
{
  temp = transCharToInt(databuffer, 17, 19) * 25.40 * 0.01;
  return temp;
}

float RainfallOneDay()                                                               //Rainfall (24 hours)
{
  temp = transCharToInt(databuffer, 21, 23) * 25.40 * 0.01;
  return temp;
}

int Humidity()                                                                       //Humidity
{
  return transCharToInt(databuffer, 25, 26);
}

float BarPressure()                                                                  //Barometric Pressure
{
  temp = transCharToInt(databuffer, 28, 32);
  return temp / 10.00;
}

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  getBuffer();                                                                      //Begin!
  Serial.print("Wind Direction: ");
  Serial.print(WindDirection());
  Serial.println("  ");
  Serial.print("Average Wind Speed (One Minute): ");
  Serial.print(WindSpeedAverage());
  Serial.println("m/s  ");
  Serial.print("Max Wind Speed (Five Minutes): ");
  Serial.print(WindSpeedMax());
  Serial.println("m/s");
  Serial.print("Rain Fall (One Hour): ");
  Serial.print(RainfallOneHour());
  Serial.println("mm  ");
  Serial.print("Rain Fall (24 Hour): ");
  Serial.print(RainfallOneDay());
  Serial.println("mm");
  Serial.print("Temperature: ");
  Serial.print(Temperature());
  Serial.println("C  ");
  Serial.print("Humidity: ");
  Serial.print(Humidity());
  Serial.println("%  ");
  Serial.print("Barometric Pressure: ");
  Serial.print(BarPressure());
  Serial.println("hPa");
  Serial.println("");
  Serial.println("");
}

In the next sections, I will show you how to connect an ESP32 to the weather station kit and write code to retrieve weather data to display them on the Serial Monitor or a Webpage.

I recommend that you use an ESP32 instead of an Arduino. The serial communication is easier (you don’t need to change jumpers) and thanks to the Wi-Fi capabilities of the ESP32 we can have a webpage to show the weather data.

Connecting the Weather Station Kit to ESP32-C3 SuperMini

Connecting the Weather Station to an ESP32 works the same as for the Arduino. Wire the four pins of the interface board (5V, GND, TX, RX) to the pins of the Arduino UNO (5V, GND, TX, RX) as shown below:

As before, make sure that the RX and RX wires are crossed. The following table list the connections:

Interface BoardESP32-C3 SuperMini
5V5V
GG
TXRX (GPIO 20)
RXTX (GPI0 21)

For more details about the pinout of the ESP32-C3 SuperMini and its other features see our ESP32-C3 SuperMini Board tutorial.

Install ESP32 Core

If you have not programmed an ESP32 before, you first need to install the ESP32 Core to enable support for ESP32 boards within the Arduino IDE. Open your Arduino IDE and follow the steps outlined below. If you have issues, you can find more detailed instructions in our tutorial Install ESP32 core in Arduino IDE .

Additional boards manager URLs

First open the Preferences dialog by selecting “Preferences…” from the “File” menu:

Open Preferences Dialog
Open Preferences Dialog

This will open the Preferences dialog shown below. Under the Settings tab you will find an edit box at the bottom of the dialog that is labelled “Additional boards manager URLs”:

In this input field copy the following URL: “https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

This will let the Arduino IDE know, where to find the ESP32 core libraries. Next we will actually install the ESP32 core libraries using the Boards Manager.

Boards Manager

Open the BOARDS MANAGER by clicking on the board icon in the sidebar of the Arduino IDE:

Board icon in Sidebar
Board icon in Sidebar

You will see the BOARDS MANAGER appearing right to the Sidebar. Enter “ESP32” in the search field at the top and you should see two types of ESP32 boards; the “Arduino ESP32 Boards” and the “esp32 by Espressif” boards. We want the esp32 libraries by Espressif. Click on the INSTALL button and wait until the download and install is complete.

Install ESP32 Core libraries
Install ESP32 Core libraries

Once installed, your Boards Manager should look like this, though the actual version (here 3.3.7) might be different.

ESP32 core installed
ESP32 core installed

In the next step, I show you how to select the ESP32 board for the ESP32-C3 Supermini.

Select ESP32-C3 Supermini board

You can select a board from the drop-down selector under the menu bar: In the example below it shows an Arduino Uno as selected board, for instance:

Bord selector
Bord selector

Clicking on the name of the currently selected board (Arduino Uno), will open the board selection dialog. In the search box type “nologo” and select the “Nologo ESP32C3 Super Mini” as shown below:

Select “Nologo ESP32C3 Super Mini” in Board Manager

If the board is connected to your PC via USB, you should also be able to select the COM port. In the screenshot above this is COM4 but in your case it might be another COM port.

Uploading code to the ESP32-C3 Supermini board

To upload the following code examples to the ESP32-C3 Supermini board just connect the USB cable to your computer and close both jumpers (J1, J2) on the interface board as shown below:

Configuration jumpers on interface board
Configuration jumpers on interface board

There is no need to change the jumpers when flashing or running the ESP32 or the Weather station.

ESP32 Code: Read data from Weather Station

The following code example shows you how to read weather data from the weather station. It prints the data to the Serial Monitor. If you run the code you should see output similar to the following:

I was using the new ESP32 core 4.0.0-alpha1 but the code should run with pretty much any ESP32 core. But you need to have “USB CDC On Boot” enabled under Tools, otherwise you will not see any printout on the Serial monitor.

// board:
// - Nologo ESP32C3 Super Mini
// tools:
// - USB CDC On Boot : Enabled
// esp32 core:
// - version: 4.0.0-alpha1

char databuffer[35];

// ── UART ────────────────────────────────────────────────────────────────────

bool getBuffer() {
  int index = 0;
  unsigned long start = millis();
  while (index < 35) {
    if (millis() - start > 5000) {
      Serial.println("[ERROR] Sensor timeout - check wiring");
      return false;
    }
    if (Serial0.available()) {
      databuffer[index] = Serial0.read();
      if (databuffer[0] != 'c')
        index = -1;  // re-sync to 'c' start byte
      index++;
    }
  }
  return true;
}

// ── PARSING ─────────────────────────────────────────────────────────────────

int parseInt(int start, int stop) {
  int result = 0;
  for (int i = start; i <= stop; i++)
    result = result * 10 + (databuffer[i] - '0');
  return result;
}

int parseTempF() {
  return (databuffer[13] == '-')
           ? -(((databuffer[14] - '0') * 10) + (databuffer[15] - '0'))
           : ((databuffer[13] - '0') * 100) + ((databuffer[14] - '0') * 10) + (databuffer[15] - '0');
}

// ── MEASUREMENTS ────────────────────────────────────────────────────────────

int WindDirection() {
  return parseInt(1, 3);
}
float WindSpeedAverage() {
  return 0.44704f * parseInt(5, 7);
}
float WindSpeedMax() {
  return 0.44704f * parseInt(9, 11);
}
float Temperature() {
  return (parseTempF() - 32.0f) * 5.0f / 9.0f;
}
float RainfallOneHour() {
  return parseInt(17, 19) * 0.2540f;
}
float RainfallOneDay() {
  return parseInt(21, 23) * 0.2540f;
}
int Humidity() {
  return parseInt(25, 26);
}
float BarPressure() {
  return parseInt(28, 32) / 10.0f;
}

// ── MAIN ────────────────────────────────────────────────────────────────────

void setup() {
  Serial.begin(115200);
  unsigned long t = millis();
  while (!Serial && millis() - t < 3000)
    ;
  Serial.println("[INFO] ESP32-C3 weather station starting...");
  Serial0.begin(9600, SERIAL_8N1, 20, 21);
}

void loop() {
  if (!getBuffer()) {
    delay(2000);
    return;
  }

  Serial.printf("Wind Direction:              %d°\n", WindDirection());
  Serial.printf("Wind Speed (1 min avg):      %.2f m/s\n", WindSpeedAverage());
  Serial.printf("Wind Speed (5 min max):      %.2f m/s\n", WindSpeedMax());
  Serial.printf("Temperature:                 %.1f °C\n", Temperature());
  Serial.printf("Humidity:                    %d%%\n", Humidity());
  Serial.printf("Barometric Pressure:         %.1f hPa\n", BarPressure());
  Serial.printf("Rainfall (1 hr):             %.2f mm\n", RainfallOneHour());
  Serial.printf("Rainfall (24 hr):            %.2f mm\n", RainfallOneDay());
  Serial.println();
}

ESP32 Code: Webserver for Weather Station

In this next code example we will add a webserver to the previous code which will allow you to display the weather data on a webpage. Below is a screenshot of the webpage:

For this code, I suggest you create a folder, e.g. “weather-station-kit-server-dfrobot”. The folder contains three files. The main sketch “weather-station-kit-server-dfrobot.ino”, a file “weather_server.h” that contains the code for the websever and a “credentials.h” file with the login details for your Wi-Fi network.

In the following you will find the code for the three files. The filename is in the first line of the comment block for each file. We start with the main sketch “weather-station-kit-server-dfrobot.ino”:

// weather-station-kit-server-dfrobot.ino
// board:
// - Nologo ESP32C3 Super Mini
// tools:
// - USB CDC On Boot : Enabled
// esp32 core:
// - version: 4.0.0-alpha1

#include <WiFi.h>
#include <ESPmDNS.h>
#include "credentials.h"
#include "weather_server.h"

char databuffer[35];

// ── UART ─────────────────────────────────────────────────────────────────────

bool getBuffer()
{
  int index = 0;
  unsigned long start = millis();
  while (index < 35)
  {
    if (millis() - start > 5000)
    {
      Serial.println("[ERROR] Sensor timeout");
      return false;
    }
    if (Serial0.available())
    {
      databuffer[index] = Serial0.read();
      if (databuffer[0] != 'c')
        index = -1;
      index++;
    }
  }
  return true;
}

// ── PARSING ──────────────────────────────────────────────────────────────────

int parseInt(int start, int stop)
{
  int result = 0;
  for (int i = start; i <= stop; i++)
    result = result * 10 + (databuffer[i] - '0');
  return result;
}

int parseTempF()
{
  return (databuffer[13] == '-')
             ? -(((databuffer[14] - '0') * 10) + (databuffer[15] - '0'))
             : ((databuffer[13] - '0') * 100) + ((databuffer[14] - '0') * 10) + (databuffer[15] - '0');
}

// ── MEASUREMENTS ─────────────────────────────────────────────────────────────

int WindDirection() { return parseInt(1, 3); }
float WindSpeedAverage() { return 0.44704f * parseInt(5, 7); }
float WindSpeedMax() { return 0.44704f * parseInt(9, 11); }
float Temperature() { return (parseTempF() - 32.0f) * 5.0f / 9.0f; }
float RainfallOneHour() { return parseInt(17, 19) * 0.2540f; }
float RainfallOneDay() { return parseInt(21, 23) * 0.2540f; }
int Humidity() { return parseInt(25, 26); }
float BarPressure() { return parseInt(28, 32) / 10.0f; }

// ── WIFI ─────────────────────────────────────────────────────────────────────

void wifiConnect()
{
  Serial.printf("[WiFi] Connecting to %s", WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\n[WiFi] Connected: http://%s\n", WiFi.localIP().toString().c_str());

  if (MDNS.begin(HOSTNAME))
  {
    Serial.printf("[mDNS] Hostname: http://%s.local\n", HOSTNAME);
  }
  else
  {
    Serial.println("[mDNS] Failed to start");
  }
}

// ── SETUP / LOOP ─────────────────────────────────────────────────────────────

void setup()
{
  Serial.begin(115200);
  unsigned long t = millis();
  while (!Serial && millis() - t < 3000)
    ;
  Serial.println("[INFO] ESP32-C3 weather station starting...");

  Serial0.begin(9600, SERIAL_8N1, 20, 21);

  wifiConnect();
  serverSetup();
}

void loop()
{
  if (WiFi.status() != WL_CONNECTED)
    wifiConnect();

  if (!getBuffer())
  {
    delay(2000);
    return;
  }

  // Populate the shared struct used by the web server
  wxData = {
      WindDirection(),
      WindSpeedAverage(),
      WindSpeedMax(),
      Temperature(),
      Humidity(),
      BarPressure(),
      RainfallOneHour(),
      RainfallOneDay(),
      true};

  // Serial.printf("Wind Dir: %d°  Avg: %.2f m/s  Max: %.2f m/s\n",
  //               wxData.windDir, wxData.windAvg, wxData.windMax);
  // Serial.printf("Temp: %.1f°C  Hum: %d%%  Pressure: %.1f hPa\n",
  //               wxData.tempC, wxData.humidity, wxData.pressure);
  // Serial.printf("Rain 1h: %.2f mm  Rain 24h: %.2f mm\n\n",
  //               wxData.rain1h, wxData.rain24h);

  serverHandle();
}

I have commented out the printing to the Serial monitor but you can use it for debugging, should the webserver not work and you don’t see the webpage.

Next we have the file with the code for “weather_server.h”:

// weather_server.h

#pragma once

#include <WebServer.h>

// ── SHARED DATA STRUCT ───────────────────────────────────────────────────────

struct WeatherData {
  int windDir;
  float windAvg;
  float windMax;
  float tempC;
  int humidity;
  float pressure;
  float rain1h;
  float rain24h;
  bool valid = false;
};

WeatherData wxData;
WebServer server(80);

// ── HTML PAGE ────────────────────────────────────────────────────────────────

static const char HTML[] PROGMEM = R"rawhtml(
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Weather Station</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      background: #e8f4f8;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
    }
    .card {
      background: white;
      border-radius: 16px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.1);
      width: 100%;
      max-width: 480px;
      overflow: hidden;
    }
    .header {
      background: linear-gradient(135deg, #1a6b9a, #2196a6);
      color: white;
      padding: 24px;
      text-align: center;
    }
    .header h1 { font-size: 1.5rem; font-weight: 600; }
    .header p  { opacity: 0.8; font-size: 0.85rem; margin-top: 4px; }
    .grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 1px;
      background: #e2e8f0;
    }
    .cell {
      background: white;
      padding: 18px 16px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .cell .icon  { font-size: 1.4rem; }
    .cell .label { font-size: 0.75rem; color: #718096; text-transform: uppercase; letter-spacing: 0.05em; }
    .cell .value { font-size: 1.4rem; font-weight: 700; color: #2d3748; }
    .cell .unit  { font-size: 0.8rem;  color: #a0aec0; }
    .footer {
      padding: 12px 20px;
      text-align: center;
      font-size: 0.78rem;
      color: #a0aec0;
      background: #f7fafc;
    }
    .dot {
      display: inline-block;
      width: 8px; height: 8px;
      border-radius: 50%;
      background: #48bb78;
      margin-right: 5px;
      animation: pulse 2s infinite;
    }
    @keyframes pulse {
      0%,100% { opacity: 1; } 50% { opacity: 0.3; }
    }
  </style>
</head>
<body>
  <div class="card">
    <div class="header">
      <h1>&#127780; Weather Station</h1>
      <p id="status">Loading...</p>
    </div>
    <div class="grid" id="grid">
      <div class="cell"><span class="icon">&#x1F9ED;</span><span class="label">Wind Direction</span><span class="value" id="windDir">--</span><span class="unit">degrees</span></div>
      <div class="cell"><span class="icon">&#x1F4A8;</span><span class="label">Wind Speed (avg)</span><span class="value" id="windAvg">--</span><span class="unit">m/s</span></div>
      <div class="cell"><span class="icon">&#x26A1;</span><span class="label">Wind Speed (max)</span><span class="value" id="windMax">--</span><span class="unit">m/s</span></div>
      <div class="cell"><span class="icon">&#x1F321;</span><span class="label">Temperature</span><span class="value" id="temp">--</span><span class="unit">&deg;C</span></div>
      <div class="cell"><span class="icon">&#x1F4A7;</span><span class="label">Humidity</span><span class="value" id="hum">--</span><span class="unit">%</span></div>
      <div class="cell"><span class="icon">&#x1F30A;</span><span class="label">Pressure</span><span class="value" id="pres">--</span><span class="unit">hPa</span></div>
      <div class="cell"><span class="icon">&#x1F327;</span><span class="label">Rainfall (1 hr)</span><span class="value" id="rain1">--</span><span class="unit">mm</span></div>
      <div class="cell"><span class="icon">&#x1F327;</span><span class="label">Rainfall (24 hr)</span><span class="value" id="rain24">--</span><span class="unit">mm</span></div>
    </div>
    <div class="footer"><span class="dot"></span><span id="updated">Waiting for first reading...</span></div>
  </div>
  <script>
    function update() {
      fetch('/data')
        .then(r => r.json())
        .then(d => {
          if (!d.valid) { document.getElementById('status').textContent = 'Waiting for sensor...'; return; }
          document.getElementById('status').textContent  = 'Makerguides';
          document.getElementById('windDir').textContent = d.windDir;
          document.getElementById('windAvg').textContent = d.windAvg.toFixed(2);
          document.getElementById('windMax').textContent = d.windMax.toFixed(2);
          document.getElementById('temp').textContent    = d.tempC.toFixed(1);
          document.getElementById('hum').textContent     = d.humidity;
          document.getElementById('pres').textContent    = d.pressure.toFixed(1);
          document.getElementById('rain1').textContent   = d.rain1h.toFixed(2);
          document.getElementById('rain24').textContent  = d.rain24h.toFixed(2);
          document.getElementById('updated').textContent = 'Updated: ' + new Date().toLocaleTimeString();
        })
        .catch(() => document.getElementById('status').textContent = 'Connection error');
    }
    update();
    setInterval(update, 10000);   // refresh data every 10 seconds
  </script>
</body>
</html>
)rawhtml";

// ── ROUTE HANDLERS ───────────────────────────────────────────────────────────

void handleRoot() {
  server.send_P(200, "text/html", HTML);
}

void handleData() {
  char json[256];
  if (!wxData.valid) {
    server.send(200, "application/json", "{\"valid\":false}");
    return;
  }
  snprintf(json, sizeof(json),
           "{\"valid\":true,\"windDir\":%d,\"windAvg\":%.2f,\"windMax\":%.2f,"
           "\"tempC\":%.1f,\"humidity\":%d,\"pressure\":%.1f,"
           "\"rain1h\":%.2f,\"rain24h\":%.2f}",
           wxData.windDir, wxData.windAvg, wxData.windMax,
           wxData.tempC, wxData.humidity, wxData.pressure,
           wxData.rain1h, wxData.rain24h);
  server.send(200, "application/json", json);
}

// ── PUBLIC API ───────────────────────────────────────────────────────────────

void serverSetup() {
  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
  Serial.println("[HTTP] Server started on port 80");
}

void serverHandle() {
  server.handleClient();
}

And finally a small file with credentials for your Wi-Fi network and the hostname for the webpage:

// credentials.h

#pragma once

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";
const char*  HOSTNAME = "weatherstation";  // http://weatherstation.local

You must replace the “SSID” and “PASSWORD” with the login details for your local Wi-Fi network.

If you upload and run the code you should see the following printed to your Serial Monitor:

Copy the URL “http://weatherstation.local” into the address bar of your browser and you should see the webpage for your weather station. If you play with the wind speed or wind direction sensor, or breath on the temperature sensor, you should see the data changing.

ESP32 Code: Webserver for Weather Station with History

The last code example implements a Webpage for the weather station that also displays historic weather measurements. The screenshot below shows you how the new webpage looks like:

Weather Station Webpage with History Data
Weather Station Webpage with History Data

It extends the previous code example with a graph at the bottom that shows you the change in the different weather data (temperature, Humidity, Wind Speed, …) over the last 5 days. You can switch between the different weather data by clicking on the corresponding buttons above the plot.

The actual code for this Weather Station Webpage is a bit too big to post here but you can find it on my Github repo listed below:

https://github.com/maet3608/weather-station-kit-server-historic-dfrobot

Just clone the repo, change the “credentials.h” file and you are ready to go.

Conclusions

In this tutorial you learned how to assemble the DFRobot Weather Station Kit and connect it to an Arduino or ESP32. Furthermore, you learned how to retrieve weather data and how to display them on the Serial Monitor or on a Webpage.

Note that the interface board of the Weather Station Kit must not be exposed to the weather. You need to either put it in a sealed, watertight container or place it indoors.

Furthermore, I suggest that you mount a net or grid on top of rain bucket, so that it doesn’t get easily blocked by leaves. Otherwise, you may have to clean it frequently.

If you have any questions feel free to leave them in the comment section.

Happy Tinkering ; )