Measuring wind speed

Hello,

I am in the process of setting up an ESP32-based weather station using the Aliexpress versions of the sparkfun weather meter sensors.

I have based my configuration largely on this project: https://gist.github.com/bassicrob/93fb0adc95261217fd4677dd7c34381c which is originally based on this, I believe.

But I am struggling with the wind sensor, specifically the calibration filters. Why multiply by 0.04973? If the sensor updates every 15 seconds, and lets say it read 15 pulses, it should output 60 pulses/min. If 1 pulse per second is 1.492 MPH, then shouldnât the formula be: 60 / 60 = pulses/sec and pulses/sec * 1.492, so the wind speed is 1.492MPH? Why does the formula given have 60 divided by 2? Please help me understand where I am wrong. Maybe the author @bassicrob could chime in? Thank you!

``````sensor:
- platform: pulse_counter
pin:
# pin D5
number: GPIO14
mode: INPUT_PULLUP
unit_of_measurement: 'mph' ##change to m/s if metric
name: 'Wind sensor'
icon: 'mdi:weather-windy'
id: my_wind
count_mode:
rising_edge: DISABLE
falling_edge: INCREMENT
internal_filter: 50us
update_interval: 15s
#rotations_per_sec = pulses/2/60
#circ_m=0.09*2*3.14 = 0.5652
#mps = 1.18*circ_m*rotations_per_sec
#mps = 1.18*0.5652/2/60 =0,0055578
filters:
# - multiply: 0.0055578 #use for m/s
# - multiply: 2.237 #m/s to mph
# - sliding_window_moving_average:
#     window_size: 4
#     send_every: 1
- multiply: 0.04973 #1.492mph switch to close 1/sec per spec, pulse/sec (/60/2)*1.492
# - multiply: 0.0124327986 #m/s * mph conversion
``````

EDIT: As Iâve done more research I have determined that the original formulas in the config (rotations_per_sec, circ_m) have to do with calibration of the anemometer using rotational physics, if Iâm not mistaken. If the manufacturer specifies 1.492mph for the switch to close once per second, then shouldnât the multiply filter simply be 1 / 60 * 1.492 = ~0.024867?

First off, it looks like the second link in your code was largely based on my code, but better documented, and my code aside from the wind direction was largely based on @mkuoppaâs code. He/She did the original calculation and I adapted it for mph (multiplying the m/s calc to mph). My sensor had 2 reed clicks per rotation, hence the dividing by 2. Yours is double my conversion as a result. Honestly I took the calculation at face value thinking someone smarter than me had already figured it out, and never verified it myself to be accurate (frankly due to lack of a way to measure wind in a calibrated way!). I do know from it being in a relatively limited exposure and low elevation that I do record accurate enough wind speed for my use case as compared to my nearest PWS (which is quite near being in a large city).
You can have a read through this thread here if you havenât already for my journey, which includes link to my reddit post and gist on my stationâs development.
If you have the same sensor array and a formula that leads to better accuracy, then that is why we are a community!

3 Likes

Thank you so much for getting back with me! I may have gotten âwho borrowed whoseâ code wrong as I was not paying attention to repository dates, but of course borrowed/forked code is the beauty of open source!

I believe I may have also taken your approach of assuming smarter people had figured out a better method, perhaps due to my own lack of confidence, but as I have been working on my station I have been learning a lot about these sensors and perhaps I can contribute some helpful solutions (or be proven wrong!) Over the next couple weeks I would like to get a repository of my full code up on my GitHub, but for now I will share some relevant snippets.

I am currently testing the following configuration for the wind speed:

``````sensor:
- platform: pulse_meter
pin:
number: GPIO14
mode: INPUT_PULLUP
name: '\${loc} Windspeed Meter'
icon: 'mdi:weather-windy'
id: wind_meter
unit_of_measurement: 'mph'
accuracy_decimals: 0
timeout: 5s
filters:
- multiply: 0.02487
- sliding_window_moving_average:
window_size: 5
send_every: 5
``````

You will notice I am using pulse_meter as opposed to pulse_counter. Based on this post, my understanding is that both these sensors operate very similarly, however pulse_meter offers much better resolution. Both pulse_meter and pulse_counter output data in pulses/minute (per the documentation), so to calibrate this sensor it is only necessary to divide the output by 60 to convert to pulses/second and multiply by the calibration value. I use a sliding_window_moving_average to smooth the data little.

I currently own this anemometer and this wind vane from MISOL Electric, which sells the MS-WH-SP-WS02 kit.

Now a little detective work on the common sparkfun weather station: A quick ImportYeti search revealed that sparkfun is a customer of Elektor. hugokernel on github has documented very well his use of the Elektor weather station (originally from here). This company owns a store where they sell this station, and the manufacturer is listed as âMiSolâ so I think it is reasonable to assume that this is the same station as mine, simply imported and rebranded.

The most common datasheet floating around for this kit, which hugokernel provides, references âArgent Data Systemsâ, which also resells their own import of the station (here is the original Argent datasheet). Curiously, ImportYeti indicates that Argent is supplied by Fine Offset Electronics, and the similar Shenzhen Fine Offset Electronics A supplies sparkfun.

Per these datasheets, the anemometer is calibrated such that:

A wind speed of 2.4km/h (1.492mph) causes the switch to close once per second.

However, I also contacted the seller MISOL Electric on Ali, and they provided the following data:

A lot of times the verbiage in these documents is not great due to translation; if wind speed âdataâ is â5â, that would correspond to 5 pulses/second. According to this sheet, it appears that the calibration is 0.34 m/s (1.2km/h) at 1 pulse/second, which is half the previous datasheets.

I believe that âswitch closing once per secondâ refers to pulses, which is why I did not divide by two. In sparkfunâs example weather station firmware here they use the following code:

``````//Returns the instataneous wind speed
float get_wind_speed()
{
float deltaTime = millis() - lastWindCheck; //750ms

deltaTime /= 1000.0; //Covert to seconds

float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4

windClicks = 0; //Reset and start watching for new wind
lastWindCheck = millis();

windSpeed *= 1.492; //4 * 1.492 = 5.968MPH

/* Serial.println();
Serial.print("Windspeed:");
Serial.println(windSpeed);*/

return(windSpeed);
}
``````

They simply multiply pulses/second by the calibration.

I may do some field tests in my car to confirm this data.

Edit: Clarified calibration and added example from sparkfun.

1 Like

So I took my anemometer out for a drive, and my calibration of 1 / 60 * 1.492 was indeed incorrect. Per the data sent by MISOL Electric, a wind speed of 1.492mph causes one rotation per second.

So I am using the following calibration:

``````(pulses per minute / 60) / (pulses per rotation) * (mph per pulses per second)

1 / 60 / 2 * 1.491424 = 0.0124285

- multiply: 0.0124285
``````

Note: Above I use 1.491424mph as that is the most (decimal) accurate conversion from the original datasheet value of 2.4km/h.

@bassicrob, your current code on github looks like this:

``````    filters:
# - multiply: 0.0055578 #use for m/s
# - multiply: 2.237 #m/s to mph
# - sliding_window_moving_average:
#     window_size: 4
#     send_every: 1
- multiply: 0.04973 #1.492mph switch to close 1/sec per spec, pulse/sec (/60/2)*1.492
# - multiply: 0.0124327986 #m/s * mph conversion
``````

Would I be correct in assuming the last line should be the one uncommented? 0.04972 is quadruple the correct calibration, although I do not know your exact use case.

I found this VERY helpful comment from @mkuoppa on his GitHub: link, where he explains the calibration in his original code.

My understanding is that he calibrated his anemometer using the formula for radial velocity (calculator here and the 1.18 multiplication accounts for drag. Very novel!

Due to the available data for my particular weather meter, I will be using the datasheet calibration to set my multiplication factor in ESPhome, however I will continue to compare my values to known wind speeds in an effort to validate the math.

Until I get everything up on GitHub, here is my code so far, including my solution for computing the Beaufort windspeed!

PLEASE NOTE: The issue with this code is my use of a moving average with pulse_meter. Because the window size is 5, when the pulse meter times out to zero, the sensor does not report 0. This is only an issue when there is no wind, and I am currently looking for a solution.

As it stands, Windspeed Meter reports a very âhigh resolutionâ output of wind speed, and I am using the template Windspeed sensor to put out periodic averages. The issue with this method is that throttle_average does not account for the frequency of data it receives, so for example, if the wind blows for 30 seconds and stops, if throttle_frequency is set to 60 seconds, it will only average the values received in the first 30 seconds, and will not consider the half minute of inactivity. Also looking for a better solution here, for now I use a short update interval to correct for this.

``````  - platform: pulse_meter
pin:
number: GPIO14
mode: INPUT_PULLUP
name: '\${loc} Windspeed Meter'
icon: 'mdi:weather-windy'
id: wind_meter
unit_of_measurement: 'mph'
accuracy_decimals: 0
timeout: 5s
filters:
- multiply: 0.0124285 #1.492mph per rotation so 1 / 60 / 2 * 1.491424
- sliding_window_moving_average:
window_size: 5
send_every: 1

- platform: template
name: '\${loc} Windspeed'
icon: 'mdi:weather-windy'
id: wind_meter_avg
lambda: return id(wind_meter).state;
unit_of_measurement: 'mph'
update_interval: 5s
filters:
- throttle_average: 5s
on_value:
lambda: |-
if (id(wind_meter_avg).state < 1) {
id(wind_scale).publish_state("Calm");
} else if (id(wind_meter_avg).state > 0 && id(wind_meter_avg).state < 4) {
id(wind_scale).publish_state("Light Air");
} else if (id(wind_meter_avg).state > 3 && id(wind_meter_avg).state < 8) {
id(wind_scale).publish_state("Light Breeze");
} else if (id(wind_meter_avg).state > 7 && id(wind_meter_avg).state < 13) {
id(wind_scale).publish_state("Gentle Breeze");
} else if (id(wind_meter_avg).state > 12 && id(wind_meter_avg).state < 19) {
id(wind_scale).publish_state("Moderate Breeze");
} else if (id(wind_meter_avg).state > 18 && id(wind_meter_avg).state < 25) {
id(wind_scale).publish_state("Fresh Breeze");
} else if (id(wind_meter_avg).state > 24 && id(wind_meter_avg).state < 32) {
id(wind_scale).publish_state("Strong Breeze");
} else if (id(wind_meter_avg).state > 31 && id(wind_meter_avg).state < 39) {
id(wind_scale).publish_state("Near Gale");
} else if (id(wind_meter_avg).state > 38 && id(wind_meter_avg).state < 47) {
id(wind_scale).publish_state("Gale");
} else if (id(wind_meter_avg).state > 46 && id(wind_meter_avg).state < 55) {
id(wind_scale).publish_state("Severe Gale");
} else if (id(wind_meter_avg).state > 54 && id(wind_meter_avg).state < 64) {
id(wind_scale).publish_state("Storm");
} else if (id(wind_meter_avg).state > 63 && id(wind_meter_avg).state < 73) {
id(wind_scale).publish_state("Violent Storm");
} else if (id(wind_meter_avg).state > 72) {
id(wind_scale).publish_state("Hurricane Force");
} else {
id(wind_scale).publish_state("");
}

text_sensor:
- platform: template
name: '\${loc} Beaufort Wind Scale'
icon: 'mdi:tailwind'
id: wind_scale
update_interval: never
``````

@devjklein very nice job and very appreciated !! I already have a mysol wind speed (and rain gaugeâŚ it will be next step) and I suppose is the same as your (rj11) so data sheet I assume itâs correct. I calibrate it in km/h (2,4 per rotation of 1 sec) but Iâll turn it by hand and listen how many clicks per rotations Iâll hear, since the only doubt is âhow may times the reed sensor return a pulse on every rotationâ then Iâll post result here. I suppose it will be once but looking above from MiSol seems to be twice, and your calibration consider to divide by 2 (1/60/2 *1.492).
It could be MiSol consider two pulse per rotation since the reed change state twice (from open to close when magnet is near and then from close to open as soon as magnet is away).

as mentioned before Iâll check manually on my Wind Speed sensor one complete turn (360Â°) means two pulses, you can clearly hear two âclicksâ. This means 1,2 km/h (or 0,34 m/s aprox) Is the right calibration as mentioned by MiSol.
The formula should be:
1/60 * 1,2 = 0,02
or, if you prefer:
1/60 / 2 * 2.4 =0.02 (since at 2,4 km/h the anemometer complete one turn in one second and generates two pulses)
this Is the right calibration imho

Thank you for confirming! It is weird that the datasheets use the phrase âswitch closingâ to reference one rotation, but Iâm sure thatâs a translation issue.

Here is my latest config using this calibration (I am using MPH here):

``````sensor:
#
# Wind Speed
#
- platform: pulse_meter
pin:
number: GPIO14
mode: INPUT_PULLUP
name: '\${loc} Windspeed Meter'
icon: 'mdi:weather-windy'
id: wind_meter
unit_of_measurement: 'mph'
accuracy_decimals: 1
timeout: 5s
filters:
- multiply: 0.0124285 #1.492mph per rotation so 1 / 60 / 2 * 1.491424
- sliding_window_moving_average: # Helps prevent too many datapoints
window_size: 15
send_every: 15
``````

In my last post I explained how I was using a template windspeed sensor to calculate an âaverageâ windspeed over a period of time via the throttle_average filter.

In my opinion, this method was not great due to the fact that throttle_average does not account for varying intervals between the reported windspeed from pulse_meter. I am now using the home assistant statistics platform to get my averages and gusts, as the average_linear characteristic considers the time distance between data points.

``````- platform: statistics
name: Roof Windspeed 1h Gust
entity_id: sensor.roof_windspeed_meter
state_characteristic: value_max
sampling_size: 999
precision: 1
max_age:
hours: 1
- platform: statistics
name: Roof Windspeed 24h Gust
entity_id: sensor.roof_windspeed_meter
state_characteristic: value_max
sampling_size: 999
precision: 1
max_age:
hours: 24
- platform: statistics
name: Roof Windspeed 1h Average
entity_id: sensor.roof_windspeed_meter
state_characteristic: average_linear
sampling_size: 999
precision: 1
max_age:
hours: 1
- platform: statistics
name: Roof Windspeed 10m Average
entity_id: sensor.roof_windspeed_meter
state_characteristic: average_linear
sampling_size: 999
precision: 1
max_age:
minutes: 10
``````

While I can see âlive windâ from my wind pulse_meter in the hass frontend, I am using 10 minute, and 1 and 24 hour max_ages on these statistics sensors to provide simple moving averages of the aforementioned time periods. Hereâs my frontend:

I feel that these approaches the most scientific so far, at least as far as my understanding of wind speed goes. I welcome anyoneâs questions or opinions regarding my methodology.

My full weather station config is also now available on my GitHub! https://github.com/devjklein/esphome-weatherstation

4 Likes

I think I also might need to shift calculations to HA. I do not like ESP sliding average as it is not time based but based on pulses. Being on the coast I have the benefit of three calibrated anemometers within 2 kms with a well defined calculation for wind speed and gusts.

I also have a more fundamental problem of my wemo dropping pulses and producing zero numbers. Some pull up resistors and voltage messing about needed

Hi Julian, you are fortunate to have such good existing data at the coast! What calculations do you use for speed and gusts? I am interested Have you tried using an ESP32? They have a hardware peripheral to count the pulses so they are more accurate than an ESP8266 See here: Pulse Counter Sensor â ESPHome

I agree regarding the filters, I only use sliding window moving average to reduce the number of pulses slightly, as the sensor obviously sends A LOT of pulses when spinning in decent wind. I feel that the averaging effect is close to negligible as the sensor still updates multiple times per second. When I have some more time on my hands I would like to look into adding some improved filters to the project.

For sure itâs not the best but for backyard use itâs very reliable, I love ESPHome and throttle_average is nice feature to have additional infos (wind quality), after calibrating with infos found here it became very useful for automations like awnings close, irrigation and so onâŚnot accurete as proof weather station but better than nothing. next step is to calibrate accurately the rain measurement.

station|375x500

Hi devjklein. The pulses were so erratic that I have gone back to basics and ordered a ESP32 with a decent power supply and then will take a look at pull up resistors to see if I can remove the zero pulses. And I will not run any other sensors on the unit.

The two commerical grade calibration wind meters, close to me, calculate based on:

Measurement Sensor Sampling Frequency Averaging Reporting Interval
Wind Speed Anemometer Every second 5 minutes Every 5 minutes
Wind Gust Anemometer Every second Taking max of 3-second running average Every 5 minutes

I also have a dedicated Davis Pro weather station 50 metres from me that has been running for 25 years!

For my part I have had to jump through hoops to get my reported figure vaguely close. I concluded that ESP Home might not be the place to do these calculations but lets see whether I get happier with dedicated ESP32. My filters are:

• platform: pulse_meter
pin: D5
unit_of_measurement: âKntsâ
name: âWind Speedâ
id: windspeed
icon: âmdi:weather-windyâ
internal_filter: 13us
accuracy_decimals: 0
filters:
• filter_out: 0.0
• max:
window_size: 50
send_every: 50
send_first_at: 1
• sliding_window_moving_average:
window_size: 10
send_every: 5
• multiply: 0.0325866
• calibrate_linear:
• 0.0 â 0.0
• 10.0 â 12.0
• 20.0 â 23.0
• 25.0 â 28.0
• 30.0 â 33.0

Apologies for the delayed response, but life with a newborn gets complicated!

My conversion is quadrupled because I am sampling every 15s. Considering this is using `pulse_counter` as opposed to the not-yet-available-at-time `pulse_meter`, logically it made sense to me to just multiply the conversion in ESPHome. If (when) I make the switch to `pulse_meter`, the standard conversion from m/s to mph should make sense. That is working well with my electric meter reading pulses with the IR led.

All of my statistics calcs for 3m average (which I use in front end for wind speed), 60m gust, 10m gust, and 10m avg (for long term stats) are calculated in HA and brought back into the ESPHome device for display as needed. With the recent changes to the statistics sensor, I probably do need to update my gist!

Hi @devjklein and @bassicrob for sharing your work. You helped me a lot with your code, and iâm going to replace my pulse_counter with pulse_meter.
Iâm in the process of wiring my wind sensor to my esp32. Itâs not from misol, but itâs built in the same way, a reed switch, a magnet which rotates with the wind, and it has a 1k resistor soldered inside.
I would like to remove the resistor and solder the resistor and the capacitor on the pcb.
Do you have soldered those components too, or were they build inside the wind meter?

With the wind speed sensor I have, it connects into the wind direction sensor and they share a cable which I have going to my ESP32. I did not add or remove any components inside the sensor itself, it goes directly to pins on the ESP32. For the wind speed, I am using this config:

``````  - platform: pulse_meter
pin:
number: GPIO14
mode: INPUT_PULLUP
name: '\${loc} Windspeed Meter'
icon: 'mdi:weather-windy'
id: wind_meter
unit_of_measurement: 'mph'
accuracy_decimals: 1
timeout: 5s
filters:
- multiply: 0.0124285 #1.492mph per rotation so 1 / 60 / 2 * 1.491424
- sliding_window_moving_average: # Helps prevent too many datapoints
window_size: 15
send_every: 15
``````

Do you mind sharing which sensor you got from Aliexpress?

Buried this info in one of my posts above. These are the links to the seller I bought from, the equipment has been perfectly reliable since February this year.

1 Like

Awesome, thank you!

Which wire goes to which pins? I have an orange and green wire from the anemometer. Not much documentation on it. Im assuming orange (Red?) is the âliveâ and the green is the signal? Not to sure which one to connect to which pins

Iâve found the original code @devjklein posted gives better results using `pulse_counter`
The other examples that are using `pulse_meter` seem very inconsistent in their results reporting gusts of 56mph on an almost perfectly still day, you can see on the right hand side of the graph when I changed from `pulse_counter` to `pulse_meter`