Help - How to populate multiple attachments entries in service call

Hi all,

I’m trying to do something like this in a yaml service call (short snip with only 2 lines):

service: notify.signal_jprates
data:
  message: 'test message'
  data:
    attachments: >
      {%- if
      is_state('binary_sensor.sensor1_access_control_window_door_is_open',
      'on') -%}
        - file_path/sensor1.jpg
      {% endif %}
      {%- if
      is_state('binary_sensor.sensor2_access_control_window_door_is_open',
      'on') -%}
        - file_path/sensor2.jpg
      {% endif %}

If I check the result from this template on developers tools I get the result I want:
imagem

So I thought the above yaml would be equivalent to this:

service: notify.signal_jprates
data:
  message: 'test message'
  data:
    attachments: 
      - file_path/sensor1.jpg
      - file_path/sensor2.jpg

However this unfortunately does not work as I expected, it seams to treat the template as if it was a single line string, not a multi-line one.

The underlying problem that I’m trying to solve here is that I have multiple sensors of which only a few (of course up to all of them) are normally open, basically windows and doors with outside access, and I’m trying to send a signal message with only the pictures from the opened ones, the closed ones are of no interest.

The only thing I know is that at least 1 of them is open, that’s the condition to trigger the automation, but I can’t guess how many of them, and it would be crazy to create over a dozen service calls to send a separate message for each opened door/window.

How can I dynamically construct the list of pictures for the “attachments:” section?
Any ideas appreciated.

TIA,
-jprates

Your Jinja2 template produces a list in YAML format but you can’t use a template to produce output that is intended to be interpreted as YAML.

The reason is due to the order of processing:

  1. YAML
  2. Jinja2

YAML first, Jinja2 second. By the time your template generates a YAML list, the YAML processing has already finished. So your YAML list is no longer interpreted as a list but as a string.

What you want is for your Jinja2 template to produce a list (not YAML code that is meant to be interpreted by the YAML processor as a list).

Try this in the Template Editor first to ensure it produces a proper list:

service: notify.signal_jprates
data:
  message: 'test message'
  data:
    attachments: >
      {% set ns = namespace(sensors = []) %}
      {% for s in expand('binary_sensor.sensor1_access_control_window_door_is_open',
                         'binary_sensor.sensor2_access_control_window_door_is_open')
         | selectattr('state', 'eq', 'on')
         | map(attribute='object_id') | list %}
         {% set ns.sensors = ns.sensors + [ 'file_path/{}.jpg'.format(s.split('_')[0]) ] %}
      {% endfor %}
      {{ ns.sensors }}

Notice how the Template Editor indicates the result of the template is a list:

1 Like

It works like a charm @123 , thank you so much!!!

I had to edit the file name part because unfortunately my sensors and my file names don’t obey that split format so cleanelly, but alas, i solved it with some “ifs”.

I will need to study what you said and learn your code, it’s far too advanced for me at this stage, so I really have to thank you twice for also improving my yaml knowledge.

Take care,
-jprates

1 Like

You’re welcome!

Please consider marking my post above with the Solution tag. It will automatically place a check-mark next to the topic’s title which signals to other users that this topic has been resolved. It helps users find answers to similar questions.

Here’s a brief tutorial explaining the template’s operation.

Whatever variables you modify inside a for-loop don’t retain their value outside the for-loop. To get around this limitation, the first line uses namespace to create a variable that is able to retain its value outside the for-loop. The variable is initialized as an empty list.

{% set ns = namespace(sensors = []) %}

We want to start with a collection containing the two binary_sensors and then pare it down to contain just the ones whose state is on.

expand('binary_sensor.sensor1_access_control_window_door_is_open', 'binary_sensor.sensor2_access_control_window_door_is_open')
  | selectattr('state', 'eq', 'on')

There’s a lot of information in that collection but we only want the object_id and we want it in a list format. The object_id is the name portion of an entity_id. For example, the object_id of light.kitchen is kitchen. To do all of that we use this:

map(attribute='object_id') | list

What we really want to do is do something to each one of the items within the collection. So that’s what all of this is doing using a for-loop:

{% for s in expand('binary_sensor.sensor1_access_control_window_door_is_open',
                   'binary_sensor.sensor2_access_control_window_door_is_open')
   | selectattr('state', 'eq', 'on')
   | map(attribute='object_id') | list %}

   ... do something to each one of the items within the collection ...

{% endfor %}

So what do we want to do to each item within that collection? We want to add it to the sensors list. That’s what this does, it adds a new item to the sensors list:

{% set ns.sensors = ns.sensors + [ ... something goes here ... ] %}

So what is this new item we want to add? It must look something like this:

file_path/sensor_name_goes_here.jpg

To produce that string neatly, we use python’s format method.

'file_path/{}.jpg'.format(blabla)

If the value of the variable blabla is “red_hat” then the result of format will be:

file_path/red_hat.jpg

In our example, the variable’s name is s and we only want the first part of its value that ends with an underscore. For example, we want sensor1 from sensor1_access_control_window_door_is_open. To do that, we use python’s split method to convert the string value into a list by splitting it at each underscore.

s.split('_')

However, we only want the first item in the resulting list so we do this (because the index of the first list item is zero).

s.split('_')[0]

Putting all of this together produces this line:

{% set ns.sensors = ns.sensors + [ 'file_path/{}.jpg'.format(s.split('_')[0]) ] %}

The very last line is the simplest, it displays the list that has been created within the for-loop:

{{ ns.sensors }}
2 Likes