Template help - max wind speed forecast

I have a sensor, sensor.bom_forecast_mylocation_1 that has an attribute ‘Detailed Summary’ that contains a string like so:

{{ state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary') }} output:

Partly cloudy. High (70%) chance of showers in the early morning. Winds west to northwesterly 15 to 20 km/h tending west to southwesterly 25 to 35 km/h in the morning then becoming light in the evening.

I would like to find the maximum from all occurances of numbers between ‘to’ and ‘km/h’ (or 0 if none) .

There can be anywhere from 0 to 4 occurrences of these numbers to choose a maximum from, that I have seen so far.

Can anyone assist?

That’s something for @123, i think. :slightly_smiling_face:

Yeah it’s a doozy. Gave up trying this afternoon. Maybe @petro too.

Here’s what I just spent the last way too many minutes busting my brain to come up with. It’s not pretty but it will work (assuming that the sensor you have outputs the string in the same way every time…).

{% set value = state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary') %}
{% set array = value.split('km/h')[:-1] %}
{% if array[0] is defined %}
  {% set  val0 = value | regex_findall_index('(\d+) km/h', index = 0) | int %}
{% else %}
  {% set val0 = 0 %}
{%endif%}

{% if array[1] is defined %}
  {% set  val1 = value | regex_findall_index('(\d+) km/h', index = 1) | int %}
{% else %}
  {% set val1 = 0 %}
{%endif%}

{% if array[2] is defined %}
  {% set  val2 = value | regex_findall_index('(\d+) km/h', index = 2) | int %}
{% else %}
  {% set val2 = 0 %}
{%endif%}

{% if array[3] is defined %}
  {% set  val3 = value | regex_findall_index('(\d+) km/h', index = 3) | int %}
{% else %}
  {% set val3 = 0 %}
{%endif%}

{% if array[4] is defined %}
  {% set  val4 = value | regex_findall_index('(\d+) km/h', index = 4) | int %}
{% else %}
  {% set val4 = 0 %}
{%endif%}
{% set max_wind = [(val0), (val1), (val2), (val3), (val4)] |max %}

{{max_wind }}

that will get you the highest reading from a possible 5 values.

1 Like

I never would have worked that out. Thanks amazing.

I tried it on 6 different forecasts and it works as expected (including one that just has ‘light winds’).

Now I can get an alert a day ahead to tell me the deck chairs are going to blow away (again) and do something about it.

Thanks heaps finity.

1 Like

No problem. I’m really glad it worked. That one really hurt. :exploding_head: but it was definitely fun trying to figure it out. :grin:

I spent way more time on it than necessary since the HA docs are wrong (or at least misleading). the templating section where it talks about “regex_findall_index” mentions in parentheses that “findall returns an array of matches”. After jacking around trying to figure out how this “findall” filter was supposed to work, I figured out that there is no other “findall” filter than the “regex_findall_index” filter which DOES NOT return an array that you have any access to.

After I realized that I just channeled my inner 123 & petro and fumbled my way thru till it worked.

But you know they’re probably going to jump in here anytime now and do what I did above in 5 lines of code and embarrass me, right? :wink:

1 Like

If they do I’ll still leave the solution mark with you for 1st place :1st_place_medal:

Thanks again for the time and effort you put into it. Very much appreciated.

1 Like

Here’s what I cooked up in my Template Editor:

It works with any number of "to nn km/h" items it may find in the Detailed Summary. It returns 0 if there are none.

{% set x = state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary') %}
{% set y = x.split(' km/h')[:-1] %}
{% set items = y | length %}
{% set ns = namespace(max=0) %}

{% for i in range(items) %}
  {% set  val = y[i] | regex_findall_index('to (\d+)$') | int %}
  {% set ns.max = val if val > ns.max else ns.max %}
{% endfor %}

{{ ns.max }}

It uses the same principal employed by finity’s solution by first converting the Detailed Summary into a list. Then it:

  • Determines the total number of items in the list.
  • Initializes a global variable called ns.max. The scope of this variable is valid inside and outside the for-loop.
  • It loops through all items in the list, searching for the upper wind-speed within the item.
  • It determines if the wind-speed is higher than ns.max. If it is, it becomes the new value for ns.max.
  • After the for-loop completes checking all items in the list, it exits and the maximum wind-speed is reported.
2 Likes

Very elegant 123, thanks for taking the time to help out. I appreciate it.

I’ve tested it against the 6 forecasts available to me and it works perfectly.

I like the fact that it has no limit on the number of results it can possibly find.

It’s going to take me a while to understand it hough.

Also, sorry you still get :2nd_place_medal: place chronologically, though I will be using your solution :slight_smile:

The solution might have been more compact if Home Assistant’s implementation of Jinja2 supported regex_findall. It returns a list containing the matching strings. It would probably look something like this:

{% set x = state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary') %}
{{ x | regex_findall('to (\d+) km/h') | max }}

I say “something like” because it makes a huge assumption that the list’s items are integer values (and I suspect they wouldn’t be). My guess is it would probably need to use map to convert the strings into integers before passing the resulting list on to max.

1 Like

Yeah @finity mentioned regex_findall being unsupported. I’ll consider submitting a feature request on github tomorrow.

I’m up way too late again.

I told you…:roll_eyes:

At least he didn’t do it in only 5 lines. it took him 1/3 the lines of code, not 1/6.

so, there…:stuck_out_tongue:

:wink:

1 Like

I realized I don’t need regex_findall_index. The use of string slicing is adequate as long as wind-speed never exceeds double-digits. I can also dispense with two temporary variables.

The result is still not 5 statements but 6 (instead of 9).

{% set y = state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary').split(' km/h')[:-1] %}
{% set ns = namespace(max=0) %}
{% for i in range(y | length) %}
  {% set ns.max = y[i][-2:]|int if y[i][-2:]|int > ns.max else ns.max %}
{% endfor %}
{{ ns.max }}

I could move the for-loop’s three statements onto one line, to reduce line-count to 4, but that wouldn’t be cricket. :slight_smile:

1 Like

Ah. That could be a problem. We had 156km/h winds two years ago.

Although one could use regex_findall_index in the abbreviated version, it would make the statement within the for-loop awfully long. Anyway, that 6-statement version was done for fun. It’s brevity doesn’t make it any easier to understand and it comes with the double-digit limitation.

The only thing I’d change in my original version is to eliminate two temporary variables. It reduces it to 7 statements without any loss of functionality.

{% set y = state_attr('sensor.bom_forecast_hobart_1', 'Detailed Summary').split(' km/h')[:-1] %}
{% set ns = namespace(max=0) %}

{% for i in range(y | length) %}
  {% set  val = y[i] | regex_findall_index('to (\d+)$') | int %}
  {% set ns.max = val if val > ns.max else ns.max %}
{% endfor %}

{{ ns.max }}
1 Like

I’ve been looking at regular expression evaluators on line and looking up the methods you used. I won’t say I could reproduce it for a different example but I have the beginnings of understanding how it works.

Thanks again.