Filter wind direction

Hi all
I have wind direction sensor in HA but it’s very noisy.
I use filer for smooth it but there is problem when wind is northen it will change in range 350-10 degree and all filters (lowpass, smoothing average) will show wrong data

So it gives you like 180 degrees?

The only thing I can think of is to add 360 to the value if it’s less than say 90.
And then to the filtering, and subtract 360 again.
It may cause other issues but that is my first thought.

In this case, there will be a gap in the region of 90deg.
The task is to get a smooth function from a function that has a gap in the region of 360.

You should average on the tangent, then take the arctangent if you really need degrees.

2 Likes

That’s brilliant.

Great.

Be careful that for precisely 90° and 270° (trigonometry-wise), tan will fail.
You might want to switch to cotan (cos ÷ sin) for those, or just test it out.

i find another mistake

you see i check 2 thing that raw wind direction is in range -90+90 deg and filterd tan is in (-1;+1)

This solution i try now

#sensor   readings      
  - platform: mqtt
    name: wind_dir_raw
    state_topic: "weather/windDir"
    unit_of_measurement: "°"
    value_template: "{{ value | round(1) }}"  
#filter  degrees 
  - platform: filter
    name: wind_dir_filter
    entity_id: sensor.wind_dir_raw
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05" 
#calc tangent of degrees       
  - platform: template
    sensors:
      wind_direction_tan_raw:   
         unit_of_measurement: 'tan' 
         value_template:  "{{ tan(float(states.sensor.wind_dir_raw.state)*pi/180) }}"
#filter   tangent
  - platform: filter
    name: wind_dir_tan_filter
    entity_id: sensor.wind_direction_tan_raw
    filters:
      - filter: range  
        lower_bound: "-10"
        upper_bound: "10"    
      - filter: time_simple_moving_average
        window_size: "00:05"
        
#If wind is  northen calc  direction with atan else direct filter        
        
  - platform: template
    sensors:
      wind_dir:  
          unit_of_measurement: "°"      
          value_template: >
            {% if states('sensor.wind_dir_raw')|float < 85 and states('sensor.wind_dir_tan_filter')|float < 1 %}
              {{ (atan(float(states.sensor.wind_dir_tan_filter.state))*180/pi) | round (1) }}
            {% elif states('sensor.wind_dir_raw')|float > 285 and states('sensor.wind_dir_tan_filter')|float > -1 %}  
              {{ (360-atan(float(states.sensor.wind_dir_tan_filter.state))*180/pi) | round (1) }}            
            {% else %}
              {{ (float(states.sensor.wind_dir_filter.state))| round (0) }}
            {% endif %}             

Yeah, but your wind_direction_tan_raw will fail even before entering the filter.

That being said, jinja is actually acting weirdly for those

Rounding issues when going to RAD, I guess

but range filter fix this problem

 - platform: filter
    name: wind_dir_tan_filter
    entity_id: sensor.wind_direction_tan_raw
    filters:
      - filter: range  
        lower_bound: "-10"
        upper_bound: "10"    
      - filter: time_simple_moving_average
        window_size: "00:05"
1 Like

I had some issues with this solution. namely it kept on failing, and did not give accurate results (and also my history browser gunked up totally), so I had a crack at the problem myself.
I decided to go in the direction of converting the wind vector into cartesian form and averaging the components before converting it back into polar form with atan.

My solution also takes into account the soon-to-be-breaking-changes of providing a default value for float, sin, cos and atan -functions. So far this solution has been working really well without hiccups.


sensor:
  #transform the wind direction from polar to cartesian coordinates (split into x and y components)
  - platform: template
    sensors: 
      wind_dir_cosine:
        value_template: "{{ cos(float(states.sensor.pws_wind_direction.state, 0)*pi/180, 0) | round (4) }}"
      wind_dir_sine:
        value_template: "{{ sin(float(states.sensor.pws_wind_direction.state, 0)*pi/180, 0) | round (4) }}"

  #average the components over time
  - platform: filter
    name: wind_dir_cosine_filter
    entity_id: sensor.wind_dir_cosine
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05"
        precision: 4

  - platform: filter
    name: wind_dir_sine_filter
    entity_id: sensor.wind_dir_sine
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05"
        precision: 4

   #convert the averaged sin and cos to degrees with arctan to get the average direction
   
   #check if sine is close to 0 (0.005 gives a resolution of about 0.3 degrees, which is plenty)
   #if not, proceed with arctan.
   #if sine is too close to zero, check the sign of the cosine, and round to north or south
   #since arctan only gives values between -90 and 90 degrees, we need to check which quadrant we are in to get the correct orientation
  - platform: template
    sensors: 
      wind_direction:
        unit_of_measurement: "      "
        value_template: >
          {% if states('sensor.wind_dir_sine_filter')|float > 0.005 or states('sensor.wind_dir_sine_filter')|float < -0.005 %}
            {% if states('sensor.wind_dir_cosine_filter')|float > 0 and states('sensor.wind_dir_sine_filter')|float > 0 %}
              {{ float(atan(float(states.sensor.wind_dir_cosine_filter.state, 0)/float(states.sensor.wind_dir_sine_filter.state, 1), 0)*180/pi, 0)  | round (0) }}
            {% elif states('sensor.wind_dir_cosine_filter')|float < 0 %}
              {{ 180 + float(atan(float(states.sensor.wind_dir_cosine_filter.state, 0)/float(states.sensor.wind_dir_sine_filter.state, 1), 0)*180/pi, 0)  | round (0) }}
            {% else %}
              {{ 360 + float(atan(float(states.sensor.wind_dir_cosine_filter.state, 0)/float(states.sensor.wind_dir_sine_filter.state, 1), 0)*180/pi, 0)  | round (0) }}
            {% endif %}
          {% elif states('sensor.wind_dir_cosine_filter')|float < 0 %}
            {{ (float(180)) }}
          {% else %}
            {{ (float(0)) }}
          {% endif %}

I am scraping the wind direction of a local weather station. It gives every minute one value for N, NE, E, SE, S, SW, W, NW and so on. My goal is to make the average of one hour for the wind direction.
The Yamartino-method seehttps://en.wikipedia.org/wiki/Yamartino_method seems to be very promising for this application. However, I fail at the implementation.
Nevertheless, I think the Yamartino-method is definitely worth a look!

Interesting! My solution is apparently half the work of the Yamartino method :D.
If you have a method of converting your input to degrees, you can use my solution above to get the average wind direction. Just change the window_size -parameter on both sine and cosine to “01:00”.

I’ll have to take a closer look at the rest of the algorithm to get the standard deviation for the wind direction.

This makes me wonder, if there’s a similar algorithm for wind gustiness :thinking:

True, your solution is indeed half of the methot. :smiley:

I found this paper for calculating the standard deviation of the Wind Direction. In compares 3 different methods.

Ok, so this stuck with me, and I could not let it go. So obviously I implemented the yamartino method, as it seemed simple enough. At the same time I improved a bit on the wind direction averaging that I had implemented before.
Low and behold:

sensor:
  - platform: template
    sensors:
      wind_dir_cosine:
        value_template: "{{ cos(float(states.sensor.pws_wind_direction.state, 0)*pi/180, 0) | round (4) }}"
      wind_dir_sine:
        value_template: "{{ sin(float(states.sensor.pws_wind_direction.state, 0)*pi/180, 0) | round (4) }}"


  - platform: filter
    name: wind_dir_cosine_filter
    entity_id: sensor.wind_dir_cosine
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05"
        precision: 4

  - platform: filter
    name: wind_dir_sine_filter
    entity_id: sensor.wind_dir_sine
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05"
        precision: 4
   
   #calculate wind direction with the four quadrant arctan. If the arctan gives a negative result we convert that to a compass heading by adding 360 
   #the atan2 handles the case if sine is 0, but if also cosine should be 0, atan2 returns 0. 
   #This can only happen if a wind would oscillate exactly 180 degrees in perfect sync with the sampling frequency for a full averaging window. 
   #So realistically, this always gives a valid result
  - platform: template
    sensors:
      wind_direction:
        unit_of_measurement: "°"
        value_template: >
          {% set wind_direction = float(atan2(float(states.sensor.wind_dir_sine_filter.state, 0), float(states.sensor.wind_dir_cosine_filter.state, 1), 0)*180/pi, 0)  | round (0) %}
          {% if wind_direction < 0 %}
            {{ wind_direction + 360.0 }}
          {% else %}
            {{ wind_direction }}
          {% endif %}

      #calculate the standard deviation of the wind direction using the yamartino method.
      #function returns a value in degrees with a minimum of 0 (wind blowing uniformly from one heading) and a maximum of 90 (wind oscillating back and forth eg. north and south).
      #So the dispersion tells the sector on both sides of the compass needle. So if the dispersion is 45 degrees, the total sector where the wind is blowing from is 90 degrees
      wind_dispersion:
        unit_of_measurement: "°"
        value_template: >
          {% set wind_epsilon = sqrt(1 - float(states.sensor.wind_dir_cosine_filter.state, 0)**2 - float(states.sensor.wind_dir_sine_filter.state, 0)**2 , 0) %}
          {{ float(asin( float(wind_epsilon, 0), 0) * (1.0 + (0.1547005384 * (float(wind_epsilon, 0)**3)))*180/pi, 0) | round (0) }}

So far it’s been working great, but I’ve only tested it for half a day (and it’s quite calm outside right now), so bugs might still be present.

Obviously I’m using the pws_wind_direction.state, which is the sensor for my personal weather station, but you can plug in any sensor in there, as long as it provides a value in degrees.

Also, my averaging window is right now set to really short (5min) (especially since my data comes in around every 60s), but one can easily change it in the moving average filters.

Hope you guys find this useful =)

4 Likes

Wow. This works wonderfully! At least at first glance. Let’s see how it behaves over time.
Here’s a 15 minute window, and it all looks excellent :slight_smile:

1 Like

I noticed an issue with the moving average of the sine and cosine;
The pro’s: it’s not our configuration, that’s the issue
The con’s: it’s the way home assistant states work. especially the way the filter -integration moving average works.

The issue is, that if the weather is calm (or wind is SUPER uniform), the value of the wind direction does not change. The moving average is evaluated only, when the source value changes. So if the direction does not change at all, the wind dispersion might show data, that is really old and really off. There is an issue created to address this, but so far it seems stale. Have a look at it and comment on it, if you have GitHub credentials:

There is an older issue with more discussion on it here: https://github.com/home-assistant/core/issues/57337

Okay, thats fine for me, if only the dispersion is affected. For my first on approach on an averaging of the wind direction, I had this problem with the wind direction (because I used the statistics integration).
But of cause, I hope it can be solved in the future.

One minor issue I could notice.
As you can see in the image, if I the system reboots, the average shortly went to zero. This is not happening for every reboot, but from time to time.
Up to now I could not find a nice solution on that.

image