Does this if statement cause a delay in TTS

I seem to be getting a slight delay before the else kicks in, is this because it’s evaluating the if statements ?

service: notify.alexa_media_everywhere
data:
  message: >-
    It is time to drop person x. It will take {{states('sensor.waze_travel_time')}}
    minutes to reach School and there is
           {% if states('sensor.waze_travel_time') | int >= 6 %}
         Minor traffic
        {% elif states('sensor.waze_travel_time') | int >= 8 %}
         Moderate traffic
        {% elif states('sensor.waze_travel_time') | int >= 10 %}
          Considerable traffic
        {% else %}
          No traffic
        {% endif %}
  data:
    type: announce
    method: all

Perhaps it’s because the template is generating white space and the TTS is “breathing”? Try this:

  message: >-
    {% set m = {0:'No',6:'Minor',8:'Moderate',10:'Considerable'} -%}
    {% set t = m[(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]]~" traffic" -%}
    It is time to drop person x. It will take {{ states('sensor.waze_travel_time') }}
    minutes to reach School and there is {{ t }}.

This works very well. Many thanks.

What I don’t understand is the 0 entry in the dictionary. It seems when the distance is 4 for example, 0 is still selected (which works for me) but I don’t understand why.

I would put have 4 in there. Is 0 entry default somehow ?

It’s my way of implementing the else in your logic. Put 4 in there if you know that’s going to be the actual minimum, but it’ll break if Waze ever reports 3.

The template returns the last (that’s what [-1] means) in the list of times less than or equal to that reported by the sensor: if the sensor is 7, the inner bit returns [0,6], the last of those is chosen (6) and the appropriate text generated (“Minor traffic”).

But shouldn’t the <= take care of that? If it’s smaller then 4 it will say ‘No traffic’.

Bear with me :slight_smile:

  1. Why do we evaluate only the last item ?
  2. What part is the inner bit? The set m = part ? Also what does the - mean in -%}
  3. Why does it return 0,6 ?
  4. Just to get my head around this:
{% set t = m[(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]]~"

This sets t to a value from the m dict, which got converted a list and the values within to an integer, and we choose the last value (-1). Not sure what the ~ does.

m[(m) | select, why do we index the m[] list with (m) ?

I guess in the end I do want to cater to all possible values, so I’ll set it to 0. But it would be great to understand what you are actually doing here.

Thanks again!

I don’t understand how your template ever worked. You have your if else in the wrong order.
My waze sensor has 25 minutes and it says minor traffic.

If you instead flip it around and first check for above 10 minutes:

    {%- if states('sensor.waze_travel_time') | int >= 10 -%}
         {%- set traffic = "Considerable traffic" -%}
    {%- elif states('sensor.waze_travel_time') | int >= 8 -%}
         {%- set traffic ="Moderate traffic" -%}
    {%- elif states('sensor.waze_travel_time') | int >= 6 -%}
         {%- set traffic ="Minor traffic" -%}
    {%- else -%}
         {%- set traffic ="No traffic" -%}
    {%- endif -%}

    It is time to drop person x. It will take {{states('sensor.waze_travel_time')}}
    minutes to reach School and there is {{traffic}}
1 Like

Explanation:

{% set m = {0:'No',6:'Minor',8:'Moderate',10:'Considerable'} -%}
{% set t = m[(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]]~" traffic" -%}

The inner bit:

m|select("<=",states('sensor.waze_travel_time')|int)|list

gets a list of dictionary keys from m that are less than or equal to your sensor. If your sensor is reporting 4, it’ll return [0]. If your sensor is reporting 9, it’ll return [0,6,8].

Now move out a layer:

(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]

and that returns the last item from the list above: so 0 in the first example, 8 in the second. Now we need to use that time to look up the corresponding text value by using that key as the lookup:

m[(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]]

First example returns “No”, second returns “Moderate”. Then tack " traffic" on the end using the ~ concatenation operator:

m[(m|select("<=",states('sensor.waze_travel_time')|int)|list)[-1]]~" traffic"

If you started with 4 rather than 0 as the first key in m, and your sensor reports 3, there’s no key less than or equal to that: the first step returns an empty list and the second step fails.

The - in -%} is whitespace control: basically means “don’t put a newline in here”. I don’t normally use those as templates self-condense most of the time, but the hypothesis behind this solution is that whitespace is causing the TTS delays, and you can see it in @Hellis81’s screenshots above.

1 Like

Another way to do the same thing:

service: notify.alexa_media_everywhere
data:
  message: >-
    {%- set t = states('sensor.waze_travel_time') | int(0) -%}
    {%- set z = { t >= 10:    'considerable',
                  t in [8,9]: 'moderate',
                  t in [6,7]: 'minor' }.get(true, 'no') -%}
    It is time to drop person x. It will take {{ t }} minutes to reach school and there is {{ z }} traffic.
  data:
    type: announce
    method: all
1 Like

Good catch, that will work much better! Thanks!

Very cool.

What does this last part do ? You are doing a .get method on the dictionary but what are you testing true for (or on, if t is true?)

  • The dictionary contains three keys (and three corresponding values).

  • Each key is a template that evaluates to either true or false.

  • The templates are designed so only one of the keys can evaluate to true.

  • The dictionary’s get method reports the value of whichever key is true.

  • If all keys are false then the dictionary’s get method reports its default value which is no.


EDIT

As a side note, the template used for the second and third keys can be expressed several ways depending on what you prefer. I chose to check if the value is a member of a list of integers.

t in [8,9]

However these do the same thing:

t=8 or t=9
8 <= t < 10

Right got it. I was still thinking in else terms but this select gives all keys that are less then or equal to and if I put in 4 but the Waze sensor is 3, it will not return that key.

The concatenation operator i don’t usually use and just add a string like
"{{ stuff }} traffic"

What does it add exactly? I mean I get you use it to concat strings like +, but does this also have to use with whitespace control? If I go in dev tools I can do:
this is a test {{ states('sensor.test') }} for me

But not sure how I can use the ~ in a useful way.

Anyway, it took me a while but I understand it now. It’s a pretty nice way of doing it!

Thanks for explaining, appreciate your time.

Got it, many thanks! I like that you use something like:
t in [8,9]

in a dictionary and as a key in particular, I didn’t know you could do that.

It’s useful sometimes. Certainly not needed here, could have done
...{{ t }} traffic.
instead, but I was replicating the output of each of your blocks.

You can see there are lots of ways to do things. One more, as compact as I can manage on the assumption that the sensor is never less than 4:

    {%- set t = states('sensor.waze_travel_time') | int(0) -%}
    It is time to drop person x.
    It will take {{ t }} minutes to reach school and there is
    {{ ['no','minor','moderate','considerable'][(3,(t-4)//2)|min] }} traffic.

It’s lightly bending the rules by having non-unique keys, but importantly, it works. The final state of that dictionary will only include one item with a false key, the last one to be evaluated which will overwrite any existing ones. The "false": "considerable" item gets overwritten in this example:

Yes in python at least you need to have unique keys. But I guess that jinja does it a bit differently here and there. So when you setup these dictionaries or jinja templates, you can have only one key that is false and one that is true?

Lol please explain this one to me as well…

A list that you then index by (3,(t-4)//2)|min] ?
What does 3 do ?
(t-4)//2)|min. I don’t know what that does either but
That still leaves 2 list entries ?

The final dictionary will have unique keys, but you can build it up with duplicates:

troon@laptop:~$ python
Python 3.11.6 (main, Oct  8 2023, 05:06:43) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> {1:'a',1:'b',2:'c'}
{1: 'b', 2: 'c'}
{{ ['no','minor','moderate','considerable'][(3,(t-4)//2)|min] }} traffic.

The left-hand [...] is just your list. The right-hand [...] is the index into that list.

We need the index to be 0 for 4 and 5 minutes, 1 for 6 and 7 etc.

t is the sensor value between 4 and infinity.
t-4 is therefore 0 upwards.
(t-4)//2 is the integer division of that number by 2.

So that maps (4,5) to 0, (6,7) to 1, (7,8) to 2, (9,10) to 3 and so on.

If the sensor is 20, the output would be 8, but the list isn’t that big so we limit the index to 3 with (3,x)|min where x is our calculation from above. I’m using a tuple (3,x) but you might find a list [3,x] more familiar.

It’s better to go with something that you’re going to understand when you come back to it in a year’s time, though :smiley: .

If the sensor could go below 4, this limits the index between 0 and 3 using a trick Taras taught me:

{{ ['no','minor','moderate','considerable'][((0,(t-4)//2,3)|sort).1] }} traffic.

That is ((lower, value, upper)|sort)[1] (but with dot notation) — very elegant.

By definition, all keys in a dictionary must be unique.

As explained by Troon, the dictionary that’s being defined will produce, at most, one true key but multiple false keys. However, each new false key overwrites the previous false key and so, the dictionary that’s ultimately generated has only a single false key.