Template error converting wind degrees to compass

Hello to community, I’m a newcomer of 3 weeks to HA jumping ship after 2 years with "OpenHab’ (which became exceedingly geeky & fiddly). Enjoying and coping ok with HA so far.
My System: Home Assistant Blue, Core ver core-2022.2.2, Supervisor ver 2022.01.1.

Trying to config a Template sensor to convert wind degrees (bearing) to a Compass value so that I can use it in a Lovelace entity card for weather. The code is in my config yaml.
Code verifies ok both in Developer tools and in HA but HA unable to ‘render’ (is that the term?) the Template.
Getting the error below after restart: “Invalid config for [template]: invalid template (TemplateSyntaxError: tag name expected)”

Screen shot of Template non-working code below:

I think it must be some silly syntax mistake I’m making…even though I have adopted the new coding guidelines for templates.

I made a similar Template for converting a zwave Aeotec Door sensor which is wired to a 'B+B Thermo-Technik" hardware rain sensor (it provides open/closed contacts with rain) on the roof and acts as a Rain Sensor. This template converts the ‘off/on’ states to ‘Raining/Dry’ for my lovelace card. This template sensor works perfectly. Screen shot of code:

In summary, I’d be delighted if anyone could spot the problem with my Wind Compass code and advise. Thanks in advance.

Please don’t post pictures of text. We can’t edit them.

Your if and elif statements should be at the front of the logical test, not inside them.

Hello Tom,

Uh oh, I’m in trouble with my first post… sorry about that, I’m not that used to forum posting etiquette really and just thought it would be easier to see the overall view.
I’ll repost the actual code and hope you or someone can explain/edit exactly where those else/ifs go… thanks for your patience.

# Template to convert wind direction to compass:
  - sensor: 
      - name: wind_compass
        state: >
          {% 348.76 <= if is_state('sensor.openweathermap_wind_bearing') | float <= 360.00 %}
            N
          {% 0.00 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 11.25 %}
            N
          {% 11.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 33.75 %}
            NNE
          {% 11.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 33.75 }
            NNE
          {% 33.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 56.25 %}
            NE
          {% 56.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 78.75 %}
            ENE
          {% 78.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 101.25 %}
            E
          {% 101.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 123.75 %}
            ESE
          {% 123.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 146.25 %}
            SE
          {% 146.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 168.75 %}
           SSE
          {% 168.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 191.25 %}
           S
          {% 191.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 213.75 %}
            SSW
          {% 213.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 236.25 %}
            SW
          {% 236.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 258.75 %}
            WSW
          {% 258.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 281.25 %}
            W
          {% 281.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 303.75 %}
            WNW
          {% 303.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 326.25 %}
            NW
          {% 326.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 348.75 %}
            NNW
          {% endif %}

Much better. Thanks. Like this (I’ve only fixed the first three, you must do the rest):

  - sensor: 
      - name: wind_compass
        state: >
          {% if 348.76 <= states('sensor.openweathermap_wind_bearing') | float <= 360.00 %}
            N
          {% elif 0.00 <= states('sensor.openweathermap_wind_bearing') | float <= 11.25 %}
            N
          {% elif 11.26 <= states('sensor.openweathermap_wind_bearing') | float <= 33.75 %}
            NNE
          {% 11.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 33.75 }
            NNE
          {% 33.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 56.25 %}
            NE
          {% 56.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 78.75 %}
            ENE
          {% 78.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 101.25 %}
            E
          {% 101.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 123.75 %}
            ESE
          {% 123.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 146.25 %}
            SE
          {% 146.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 168.75 %}
           SSE
          {% 168.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 191.25 %}
           S
          {% 191.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 213.75 %}
            SSW
          {% 213.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 236.25 %}
            SW
          {% 236.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 258.75 %}
            WSW
          {% 258.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 281.25 %}
            W
          {% 281.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 303.75 %}
            WNW
          {% 303.76 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 326.25 %}
            NW
          {% 326.26 <= elif is_state('sensor.openweathermap_wind_bearing') | float <= 348.75 %}
            NNW
          {% endif %}

You also need to change is_state() to states()

1 Like

Maybe I have a little shorter template for the conversion. I am using this template:

{% set directions = [ 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'] -%}
{% set idx = (states('sensor.dwd_wind_bearing') | int(0) / 22.5) | round(0) | int -%}
{{ directions[idx] }}

or even shorter:

{% set directions = [ 'N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'] -%}
{{ directions[(states('sensor.dwd_wind_bearing') | int(0) / 22.5) | round(0) | int] }}

(Compass directions are in german. So you have to change the O to an E)

Edit. Modified template to correctly filter the sensor state to an integer before division.
Ulrich

4 Likes

Works for my weather implementation from “Deutscher Wetterdienst”.

That should work for his as well.

Hello Tom,

Thanks so much, all that worked perfectly. The devilish details of syntax are very difficult when you’re a GUI preference man :wink: Anyway It’s really encouraging when moving to a new platform to get solid and clear help so quickly.
We can mark that as solved now I guess.

Hi Dylan,

Thanks for your reply too. Your alternative solution is very elegant indeed :+1:

It had crossed my mind but I hadn’t a clue how to code it like you did. I will actually try it out.
If you have the time, could you explain (or link me the relevant documentation) a little about what the syntax is doing?.. I have a general idea but I’m unsure about the details… what do those [ ] do?..

It seems to me that ‘idx’ is a variable (?) and gets assigned an integer value based on a calculation and is there something to do with arrays etc … sorry I get lost/muddled after that…
regards

I think that it should be this:

#{% set dir = 350 %}  <-- to run in developer
{% set directions = [ 'N', 'NNE', 'NNE', 'NE', 'NE', 'ENE', 'ENE', 'E', 'E', 'ESE', 'ESE', 'SE', 'SE','SSE', 'SSE', 'S', 'S', 'SSW', 'SSW', 'SW', 'SW', 'WSW', 'WSW','W', 'W', 'WNW', 'WNW', 'NW', 'NW', 'NNW', 'NNW', 'N'] -%}
{{ directions[(states('sensor.dwd_wind_bearing') | float(0) / 11.25) | int] }}
#{{ directions[ (dir / 11.25) | int] }}  <- to run in developer

Here it is in the developer:

This places the center around the 22.5 degree interval. IE for North (348.75 - 11.25) East (78.75 - 101.25) etc.

When you put something in brackets [] it becomes a list. This is a list of directions indexed 0 to 31 in this case (32 elements). You are then finding slice of the pie the number is in and that is the index into the list. By way of example 93/11.25 | int means 8.177… truncated to an to an integer. So 8. The 9th (remember 0 based) index is ‘E’ so that is the value.

Edit - fixed filters

2 Likes

@AllHailJ Description is perfect. And his debug code is a good way to to some tests in template developer.

A short description: We use 16 different compass directions. So 360°/16 is 22.5. Dividing the bearing by 22.5 give us an index into the list with the directions. N is a little special. As it appears at the beginning and the end of the list. So it will work for 0° and for 360°. The rest is Ninja templating.

I don’t know if the calculation is always correct. So that for example, it detects “S” for bearings between 168,75° and 191,25°. My use is for weather forecast. So it didn’t have to be absolutely exact. I have testet it with some bearings and that works as expected.

Indexed access is a widely used algorithm for this kind of data. I think there will be a lot sources, which will explain this in detail.

If you would like to dive deeper in templating, have a look at @petro’s posts (and a few other here at HA). There are a lot of helpfull hints and tipps. And use template developer to try some of the tipps.