Advanced Jinja2 Templating for fun and profit

Yes, it can appear in an email. If you currently have that setup you can just add it under message: in your config. You can read more about templates and notifications, here and here. :cat:

I see. I still couldn’t find the easiest way to write the code for notification when someone is home or away. So far this is what I did…

automation:
- alias: 'Ben is Home notification'
  trigger:
    platform: state
    entity_id: device_tracker.ben
    from: 'not_home'
    to: 'home'
  action:
    service: notify.email
    data:
      message: "Ben is home"
- alias: 'Luke is Home notification'
  trigger:
    platform: state
    entity_id: device_tracker.luke
    from: 'not_home'
    to: 'home'
  action:
    service: notify.email
    data:
      message: "Luke is home"
- alias: 'Ben is Away notification'
  trigger:
    platform: state
    entity_id: device_tracker.ben
    from: 'home'
    to: 'not_home'
  action:
    service: notify.email
    data:
      message: "Ben is away"
- alias: 'Luke is Away notification'
  trigger:
    platform: state
    entity_id: device_tracker.luke
    from: 'home'
    to: 'not_home'
  action:
    service: notify.email
    data:
      message: "Luke is away"

I have to repeat the same code if I want to add more devices. This is a real pain the back.

Thankyou for sharing your knowledge

I am struggling with iterating or relating to a group of devcies (not all devices)

I have a group called family that is 5 device_tracker devices PLUS another non-device_tracker object (an input_select object)

How can I iterate only through device_tracker states for the group group.family?

group.family has a state of home or not_home - which is great. I just want to iterate through only the device_tracker states in that group and identify who is home so I can use it in a notification.

Any suggestions would be appreciated.

I have tried state group.family and other variants - my issue is the jinga syntax for how to interact with the objects in a group

1 Like

Try the following single automation to replace the 4 you made:

automation:
- alias: "Presence notification"
  trigger:
    platform: state
    entity_id: device_tracker.ben, device_tracker.luke
  action:
    service: notify.email
    data:
      message: >
        {% set person = trigger.entity_id.split('.')[1] | capitalize %}
        {% if trigger.to_state.state == 'home' %}
          "{{ person }} is home"
        {% else %}
          "{{ person }} is away"
        {% endif %}

With this code, anytime you need to track an additional device, you just add it to entity_id: under trigger:.

Disclaimer: I have limited ways to test this code, so it may contain errors.

Edit: I just noticed the post I replied to is 4 months old…my bad! But oh well, it may still come in handy for somebody else :innocent:

5 Likes

@fanaticDavid

To dig up an old thread again is this still supported?

I keep getting errors in my log say trigger is not defined.

Based on the documentation, I would say it is. But I think there is a mistake in my code above: replace data with data_template

@robbiet480

sorry for digging up this old thread. but your example is really useful to me for an automation I am planning to do. You see, I have magnetic sensors on every windows and doors (interior and exterior). I want HA to send me a list of windows or doors (only the exterior) that are still open when it is raining. If I use this code…

{% for device in dict(states.sensor|groupby('state'))['open'] %}{% if loop.last and loop.length > 1 %} and {% elif not loop.first and loop.length > 1 %}, {% endif %}{{ device.name }}{% endfor %} {% if dict(states.sensor|groupby('state'))['open']|length == 1 %}is{% else %}are{% endif %} open.

…it will list down all sensors that have state = open including the interior sensors. How do I exclude the interior sensors from this list?

I created a group; group.exteriorwindows and add all the relevant sensors into this group. how do I use this group in your example?

Your help will be greatly appreciated. Thank you.

1 Like

Digging up this thread as I have the same question. I seem to be only able to iterate through the entire catergory of devices/sensors/etc. I would like to either have excludes, or more ideally, be able to iterate through a group created in Home Assistant.

For example, this works on the HA Templating tester:

{% for state in states.group -%}
  {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
  {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}

But if I try to drill down to a specific group, it fails:

{% for state in states.group.family -%}
  {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
  {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}

Any ideas @robbiet480 ?

Try here…

1 Like

Thanks I’ll take a look!

So in the end I used @robbiet480 's idea plus a little modification to iterate through only the windows of my binary_sensors and report which are open:

# Iterate through binary_sensors which are windows, and that are on (i.e. open)
{% for state in states.binary_sensor if 'Window' in state.name and state.state == 'on' %}
  # If this not the last device add a comma. Otherwise, use the word "and". Don't do either if there's only 1 device
  {%- if loop.last and loop.length-1 > 1 %} and 
    {% elif not loop.first and loop.length-1 > 1 %}, 
    {% endif -%}{{ state.name }}
  # If this is the last loop and it has looped more than once, use "are", otherwise use "is".
  {%- if loop.last %}
    {%- if loop.length-1 == 1 %} is 
      {% else %} are 
    {% endif -%} open.
  {% endif -%}
{% endfor %}

What’s weird is I had to remove 1 from loop.length as it was never smaller than 2, even with only one window open. Oh and loop.length bugs out if I try to combine the two ifs at the end. Open to suggestions if I’m doing something wrong.

I use device_class on my binary sensors to find windows and doors.

https://github.com/CCOSTAN/Home-AssistantConfig/blob/master/script/speech_engine.yaml#L77-L91

Found this thread whilst searching for info on Hassio jinja2 coding, wished you’d kept this going @robbiet480 We need a ‘cookbook of sorts’, I can’t find anything on the internet like that. I seem learn more by looking through code, working and adapting it for my own use.

I’m not sure where you are looking but there is about 8 bazillion template examples all across this forum and even in the cookbook docs

2 Likes

Every time I try something with {%- macro lalalala() -%} I get errors.
Is there any custom component needed to use macro?

you need to finish the macro, and macros need to be created in every field that it is used

{%- macro lalalala() %}
Lalalala
{%- endmacro %}
{{ lalalala() }}

Sorry for the questions. Ill try to understand the way of working. :slight_smile: want learn

When I try this in developer Tools > Template its working:

{%- macro message() -%}
  {{ ["The sun is down, so the romantic evening is started!",
  "The sunset has begun, i turn on the lights for you",
  "I think it will begun dak inside the house, let me turn o",
  "Make some tea, i will help you with the lights",
  "Sun?, sun?, wherever you are? Ill turn on the lights inside",
  "There are no lights on! so let me do it for you!"
  ] | random }}
{%- endmacro -%} 

"{{ message() }}"

When I add this into a test automation I get error:


while scanning for the next token
found character '%' that cannot start any token
  in "/config/config/automation/test/test.yaml", line 15, column 2

Here the automation to test my yaml script:

- alias: TEST BUTTON ON 1
  trigger:
    - platform: state
      entity_id: switch.newkaku_014e5906_a
      to: 'on'

  action:
  - service: script.turn_on
    entity_id: script.alexa_speak_engine
    data_template:
      variables:
#        volume: '0.5'
        message: "{{ message() }}"

{%- macro message() -%}
  {{ ["The sun is down, so the romantic evening is started!",
  "The sunset has begun, i turn on the lights for you",
  "I think it will begun dak inside the house, let me turn o",
  "Make some tea, i will help you with the lights",
  "Sun?, sun?, wherever you are? Ill turn on the lights inside",
  "There are no lights on! so let me do it for you!"
  ] | random }}
{%- endmacro -%}

"{{ message() }}"

when I remove the macro part and add a message “this is a test” I hear the message at my Alexa Echo.

You have to have the macro built into the field. You can’t place it in a different location and expect it to work anywhere in your config. This is a limitation of jinja and yaml

- alias: TEST BUTTON ON 1
  trigger:
    - platform: state
      entity_id: switch.newkaku_014e5906_a
      to: 'on'

  action:
  - service: script.turn_on
    entity_id: script.alexa_speak_engine
    data_template:
      variables:
#        volume: '0.5'
        message: >
          {%- macro message() -%}
            {{ ["The sun is down, so the romantic evening is started!",
            "The sunset has begun, i turn on the lights for you",
            "I think it will begun dak inside the house, let me turn o",
            "Make some tea, i will help you with the lights",
            "Sun?, sun?, wherever you are? Ill turn on the lights inside",
            "There are no lights on! so let me do it for you!"
            ] | random }}
          {%- endmacro -%}
          {{ message() }}

So to be honest, a macro wouldn’t be useful at this point because you aren’t calling it more than once.

- alias: TEST BUTTON ON 1
  trigger:
    - platform: state
      entity_id: switch.newkaku_014e5906_a
      to: 'on'

  action:
  - service: script.turn_on
    entity_id: script.alexa_speak_engine
    data_template:
      variables:
#        volume: '0.5'
        message: >
            {{ ["The sun is down, so the romantic evening is started!",
            "The sunset has begun, i turn on the lights for you",
            "I think it will begun dak inside the house, let me turn o",
            "Make some tea, i will help you with the lights",
            "Sun?, sun?, wherever you are? Ill turn on the lights inside",
            "There are no lights on! so let me do it for you!"
            ] | random }}
1 Like

thank you for the answer. Im try to understand because CCOSTAN have a very nice speech_engine and want us that as well. But first need to understand the way how macro work.

1 Like

you could add a dash at the end of {{ device.name }} for remove spacing in output:

# Loop over devices with a state of not_home
{% for device in dict(states.device_tracker|groupby('state'))['not_home'] %}
  # If this not the last device add a comma. Otherwise, use the word "and". Don't do either if there's only 1 device
  {% if loop.last and loop.length > 1 %} and {% elif not loop.first and loop.length > 1 %}, {% endif %}
  # Print the device name
  {{ device.name -}}
{% endfor %} {% if dict(states.device_tracker|groupby('state'))['not_home']|length == 1 %}is{% else %}are{% endif %} away.
# The line above will output 'is' or 'are' depending on how many devices are in the list.
1 Like