Weather station modified with an ESP32

Here is my weather station project. This is a cheap wireless weather station I modified with an ESP32 & programmed with the wonderful ESPHome.
I choose to completely remove the original electronics because I wanted to receive data from the sensors at a shorter interval and I can’t be bothered decoding the wireless signal sent.

This is the cheap weather station I purchased. I will not be using the battery powered LCD display.

Weaknesses of this weather station is construction quality.
The metal stand is not stainless steel, nor galvanised or aluminium.
The plastic sensors are likely to breakdown in sunlight UV (as I have seen on another unit).

I painted the sensor units with outdoor rated white spray paint.

The sensors;

Anemometer (Wind speed)
This is a wind cup anemometer and the number of rotations per time period can be used to calculate wind speed.
I found this unit has a magnetic reed switch and a magnet. Two pulses are given per full rotation of the wind cups.

Bearing direction (Wind vane)
This uses 4 magnetic reed switches, for North, East, South & West.
When pointed North, the North reed switch closes and connects a resistor in parallel to the sensor’s wires.
Each direction has a different resistance per direction.
Directions between, e.g. North East, are the resistance value of two resistors in parallel.

I can’t remember off the top of my head what the resistor values were. I connected the sensor across a voltage divider and used an ADC sensor. Value ranges of the ADC correlated to a direction.

Rain gauge
This is a tipping bucket rain gauge. Rain water falls into a funnel and feels a small bucket. When the bucket fills with water it tips over under gravity and activates a magnetic reed switch. The count of bucket tips is the measured rain.

This rain gauge is 0.3mm per bucket tip.

Temperature + Humidity
I removed the original temperature sensor and utilised a DHT22 sensor.

esphome:
  name: weather-station-esp32
  friendly_name: weather_station_esp32

esp32:
  board: esp32dev
  framework:
    type: arduino

#Buch of stuff here.

#Pinouts:
  #DHT22 - 18/D18
  #Anemometer - 32/D32
  #Wind direction - 34/D34
  #Rain gauge - 33/D33

button:
  - platform: template
    name: "Reset total rain"
    id: stationraintotal_reset
    icon: "mdi:rotate-left"
    on_press:
      - pulse_counter.set_total_pulses:
          id: stationrain
          value: !lambda 'return 0;'


sensor:
  # DHT temp + humidity sensor
  - platform: dht
    pin: GPIO18
    model: DHT22
    temperature:
      name: "Station Temperature"
      id: stationtemperature
      filters:
        filter_out: nan
    humidity:
      name: "Station Humidity"
      id: stationhumidity
      filters:
        filter_out: nan
    update_interval: 120s
    

  - platform: absolute_humidity
    name: Station Absolute Humidity
    temperature: stationtemperature
    humidity: stationhumidity
    filters:
      filter_out: nan

  # Wind anemometer
  # 2 Pules per rotation
  - platform: pulse_counter
    name: Station Wind Speed
    id: stationwindspeed
    pin:
      number: GPIO32
      inverted: true
      mode:
        input: true
        pullup: true
    update_interval: 10s
    filters:
      - lambda: if (x < 0.001) return x; else return 1.761 / (1 + (x / 2 * 0.02010619298) ) + 3.013 * (x / 2 * 0.02010619298);
      - sliding_window_moving_average:
          window_size: 12
          send_every: 6
    unit_of_measurement: 'Km/h'
    device_class: wind_speed
    on_value:
      then:
        - component.update: stationknots

  #Km/h to knots
  - platform: template
    name: Station wind speed knots
    id: stationknots
    lambda: |-
      return id(stationwindspeed).state * 0.53996;
    unit_of_measurement: 'knt'

  
  # Feels like temperature
  - platform: template
    name: "Feels Like Temperature"
    id: stationfeelslike
    update_interval: 120s
    unit_of_measurement: '°C'
    filters:
      filter_out: nan
    lambda: |-
      float T = id(stationtemperature).state;  // Replace with your temperature sensor
      float RH = id(stationhumidity).state;  // Replace with your humidity sensor
      float WS = id(stationwindspeed).state;  // Replace with your wind speed sensor
      float HI;
      T = T * 9/5 + 32;  // Convert temperature from Celsius to Fahrenheit for the formula
      if (T < 80.0) {
        HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (RH*0.094));
      } else {
        HI = -42.379 + 2.04901523*T + 10.14333127*RH - 0.22475541*T*RH - 6.83783*pow(10,-3)*pow(T,2) - 5.481717*pow(10,-2)*pow(RH,2) + 1.22874*pow(10,-3)*pow(T,2)*RH + 8.5282*pow(10,-4)*T*pow(RH,2) - 1.99*pow(10,-6)*pow(T,2)*pow(RH,2);
        if (RH < 13 && T >= 80 && T <= 112) {
          float adjust = ((13-RH)/4) * sqrt((17-abs(T-95))/17);
          HI -= adjust;
        } else if (RH > 85 && T >= 80 && T <= 87) {
          float adjust = ((RH-85)/10) * ((87-T)/5);
          HI += adjust;
        }
      }
      return (HI - 32) * 5/9;  // Convert back to Celsius

  
  
  #Rain gauge
  - platform: pulse_counter
    pin:
      number: GPIO33
      inverted: true
      mode:
        pullup: true
        input: true
    unit_of_measurement: 'mm/min'
    name: 'Station Rain'
    id: stationrain
    update_interval: 60s
    accuracy_decimals: 1
    filters:
      - debounce: 0.05s
      - multiply: 0.3  # pulses x 0.3mm
    total:
      unit_of_measurement: 'mm'
      name: 'Station Total Rain'
      accuracy_decimals: 1
      filters:
        - multiply: 0.3

  #Wind direction
  - platform: adc
    name: "Station Wind Dir"
    id: station_winddirection
    internal: true
    pin: 34
    accuracy_decimals: 0
    unit_of_measurement: 'ADC'
    raw: true
    filters:
      - multiply: 1.0
      - median:
          window_size: 6
          send_every: 6
          send_first_at: 1
  
  - platform: template
    name: "Station Wind Direction bearing"
    id: stationwinddirection_int
    unit_of_measurement: '°' 
    lambda: |-
      if (id(station_winddirection).state > 1500 && id(station_winddirection).state <= 1700) {
        return 90.0; //North - offset to west
      }
      else if (id(station_winddirection).state > 650 && id(station_winddirection).state <= 800) {
        return 135.0; //North East
      }
      else if (id(station_winddirection).state > 900 && id(station_winddirection).state <= 1100) {
        return 180.0; //East
      }
      if (id(station_winddirection).state > 800 && id(station_winddirection).state <= 900) {
        return 225.0; //South East
      }
      if (id(station_winddirection).state > 1900 && id(station_winddirection).state <= 2300) {
        return 270.0; //South
      }
      if (id(station_winddirection).state > 1700 && id(station_winddirection).state <= 1900) {
        return 315.0; //South West
      }
      if (id(station_winddirection).state > 2300 && id(station_winddirection).state <= 2500) {
        return 0.0; //West
      }
      if (id(station_winddirection).state > 1100 && id(station_winddirection).state <= 1500)  {
        return 45.0; //North West
      }
      else {
        return 345.0; //Fault
      }
    update_interval: 60s

This is an ESP32 module from AliExpress.
On the circuit board is terminal connectors, a power switch, 12v to 5v buck step down module and an LDR’s pcb. I have an LDR for light levels but never calibrated it (still does alright to schedule garden lights).

The station itself is mounted to my TV antenna on the back of my house. Power cables runs under a roof tile and is connected in a cupboard to a 12v PSU.

1 Like

Some cool features in this project;
I added a “feels like” temperature. This factors the temperature, humidity and wind speed.

# Feels like temperature
  - platform: template
    name: "Feels Like Temperature"
    id: stationfeelslike
    update_interval: 120s
    unit_of_measurement: '°C'
    filters:
      filter_out: nan
    lambda: |-
      float T = id(stationtemperature).state;  // Replace with your temperature sensor
      float RH = id(stationhumidity).state;  // Replace with your humidity sensor
      float WS = id(stationwindspeed).state;  // Replace with your wind speed sensor
      float HI;
      T = T * 9/5 + 32;  // Convert temperature from Celsius to Fahrenheit for the formula
      if (T < 80.0) {
        HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (RH*0.094));
      } else {
        HI = -42.379 + 2.04901523*T + 10.14333127*RH - 0.22475541*T*RH - 6.83783*pow(10,-3)*pow(T,2) - 5.481717*pow(10,-2)*pow(RH,2) + 1.22874*pow(10,-3)*pow(T,2)*RH + 8.5282*pow(10,-4)*T*pow(RH,2) - 1.99*pow(10,-6)*pow(T,2)*pow(RH,2);
        if (RH < 13 && T >= 80 && T <= 112) {
          float adjust = ((13-RH)/4) * sqrt((17-abs(T-95))/17);
          HI -= adjust;
        } else if (RH > 85 && T >= 80 && T <= 87) {
          float adjust = ((RH-85)/10) * ((87-T)/5);
          HI += adjust;
        }
      }
      return (HI - 32) * 5/9;  // Convert back to Celsius

I also log the max, median and minimum of each sensor to a Google spreadsheet at midnight every night.
This is done with an automation and helper sensors.
Google sheets then makes a lovely graph I can use. There’s also other sensors I log daily into Google sheets as you can see by the sheet tabs.
I haven’t found a good way to log wind vane direction yet.

2 Likes

That’s a cool project! Modifying a cheap weather station with an ESP32 and ESPHome to get more frequent sensor data is a smart idea. Removing the original electronics to achieve this shows your dedication to customization. I agree, the construction quality of some inexpensive weather stations can be a bit lacking, especially with the stand and sensors. If you’re looking for a more durable option, you might want to check out some weather stations from ambientweather.com. They offer a range of products with better build quality.

1 Like

A small addition.
I have figured a way to log the wind vane per day, without needing an hourly measurement (e.g. North at 12pm, East at 1pm).

What I have done is used a history_stats helper to count how many hours the wind vane is aiming in a direction over 24hrs/1 day.

This count is added to a Google sheet at midnight (bottom right graph).

This photo also shows the other sensors graphs since I started long-term logging to Google sheets.
Google sheets also offers ‘publishing’ a graph and embedding it into a webpage which is awesome!

How is it holding up?

It’s been doing great. I haven’t needed to climb a ladder and check it.
A modification I would like to make in ESPhome Code is for a wind gust measurement. Currently all the wind calculations is a sliding window average.

Data logging to Google Sheets is now over 1 year.
A few failed API requests to Google sheets occasionally, but I have backup local CSV file to copy missing data.

1 Like

Awesome, appreciated.
As fun as the project looks and I <3 ESP I’m going with the OMG option for now. It was very simple.

1 Like

Such a nicely done project. I’m looking to do the same. Would you mind sharing your code for that dashboard? I would love to try something similar

1 Like

There’s a mix of card types here.
Compass card is visual editor to add. Requires wind vane to be in numerical degrees values output.
Windrose card also requires wind vane in numerical degrees value.

Temperature, humidity, wind speed & rain is Mini-graph card.
Here’s the code for temperature:

type: custom:mini-graph-card
hours_to_show: 24
points_per_hour: 5
line_width: 2
name: Temperature
icon: mdi:home
hour24: true
animate: true
entities:
  - entity: sensor.weather_station_esp32_station_temperature
    name: Measured
    show_state: true
  - entity: sensor.weather_station_esp32_feels_like_temperature
    name: Feels like
    show_state: true
  - entity: sensor.weather_station_esp32_wind_chill
    name: Wind chill
    show_state: true
    show_fill: false
    show_line: false
    show_points: false
    show_legend: true
show:
  name: true
  extrema: true
  average: true
  fill: fade
  icon: false
  labels: hover
color_thresholds:
  - value: 0
    color: "#00A3FF"
  - value: 15
    color: "#00D084"
  - value: 20
    color: "#FF6900"
  - value: 25
    color: "#FF0000"

Here is the code for rain card today, only the time between midnight to now() is shown.

type: custom:config-template-card
entities:
  - sensor.rain_daily
card:
  type: custom:mini-graph-card
  hours_to_show: |-
    ${
      var date = new Date();
      var curHour = date.getHours() + date.getMinutes()/60.0 + date.getSeconds()/3600.0;
      curHour
    }
  points_per_hour: 5
  line_width: 2
  name: Rain today
  icon: mdi:home
  hour24: true
  entities:
    - entity: sensor.rain_daily
      name: Rain today
      show_state: true
      unit: mm
      color: cyan
  show:
    name: true
    extrema: false
    average: false
    fill: fade
    icon: false
    labels: hover

This is the bar chart rain in the last 24 hours.

type: custom:mini-graph-card
hours_to_show: 24
points_per_hour: 2
line_width: 2
name: Rain -24hrs
icon: mdi:home
hour24: true
entities:
  - entity: sensor.weather_station_esp32_station_rain
    name: Total rain
    show_state: true
    color: cyan
show:
  name: true
  extrema: true
  average: false
  fill: fade
  icon: false
  labels: hover
  graph: bar

The windrose card code:
I choose to use km/h instead of Beaufort measurement.

type: custom:windrose-card
title: Wind - 24 hour
show_current_direction_arrow: true
corner_info:
  top_left:
    label: Current windspeed
    unit: " km/h"
    entity: sensor.weather_station_esp32_station_wind_speed
data_period:
  hours_to_show: 24
max_width: 400
refresh_interval: 60
windspeed_bar_location: bottom
windspeed_bar_full: true
wind_direction_entity:
  entity: sensor.weather_station_esp32_station_wind_direction_bearing
  direction_unit: degrees
  use_statistics: false
  direction_compensation: 0
windspeed_entities:
  - entity: sensor.weather_station_esp32_station_wind_speed
    name: Windspeed
    speed_unit: kph
    use_statistics: true
output_speed_unit: kph
input_speed_unit: kph
speed_range_beaufort: false
windrose_draw_north_offset: 0
cardinal_direction_letters: NESW
matching_strategy: direction-first
center_calm_percentage: false
speed_range_max: 25
speed_range_step: 2
colors:
  rose_lines: grey
  rose_direction_letters: Grey
  rose_percentages: grey
  bar_border: hsl(200, 100%, 60%)
  bar_unit_name: grey
  bar_name: grey
  bar_unit_values: bar
  bar_percentages: black

Horizon card:

type: custom:horizon-card
time_zone: Pacific/Auckland
time_format: 24
southern_flip: false