How to pass an array variable to a script

Hello,

I’m looking to pass an array of entity_ids as a variable in an automation to a script to use as entity_ids.

Is this possible or should I look into a workaround, such as passing a string with a delimiter and use split and a for loop?

I can’t find much on this so I thought I’d ask what is the best approach.

Thanks for the help,
Will

2 Likes

you would do so through variables which are defined within the automation. Example:

- service: homeassistant.turn_on
  entity_id: script.sonos_play_web_audio
    data_template:
      variables:
        http_audio: '{{states.input_text.till_wakeup_sound.state}}'
        sonos_entity: 'media_player.till'

These variables would then be executed by the script that you have defined above.

sonos_play_web_audio:
  alias: play web file
  sequence:
    - service: media_player.play_media
      data_template:
        entity_id: '{{ sonos_entity }}'
        media_content_id: '{{ http_audio }}'

See script syntax for more information: Script Syntax

Thanks for the help; so what I’m looking to do is something more like this:

- service: homeassistant.turn_on
  entity_id: script.sonos_play_web_audio
    data_template:
      variables:
        http_audio: '{{states.input_text.till_wakeup_sound.state}}'
        sonos_entity: ['media_player.till', 'media_player.kitchen', 'media_player.family_room']

sonos_play_web_audio:
  alias: play web file
  sequence:
    - service: media_player.play_media
      data_template:
        entity_id: >-
          {% for speaker in sonos_entity %}
            {{ speaker }}
          {% endfor %}
        media_content_id: '{{ http_audio }}'

I have a decent number of Sonos zones in my house and I’d like to make my scripts as reusable as possible. I tried passing an array directly as a variable but it didn’t work so I’m wondering if what I’m wanting to do is possible in the scripting environment.

So YAML lists can be

sonos_entity:
  - media_player.kitchen
  - media_player.family_room

OR

sonos_entity: [media_player.kitchen, media_player.family_room]

Both ways work fine when I hardcode them into the script.

However, when I try to use the array as passed through as an automation variable I receive the following error message:

Invalid service data for media_player.play_media: Entity ID [‘media_player.dining_room_1’ is an invalid entity id for dictionary value @ data[‘entity_id’]. Got “[‘media_player.dining_room_1’, ‘media_player.dining_room_2’]”

I am thinking I need some kind of filter on my variable so it isn’t treated as a string but I can’t find anything relevant in the Jinja2 documentation. Any recommendations?

This is what my script looks like:

test_script:
  alias: "zTest Script"
  sequence:
    - service: media_player.sonos_unjoin
      data_template:
        entity_id: '{{speaker_location}}'

You make a very interesting point here because I also had the same kind of error when passing variables from automations to scripts. My only workaround was to create a group (composed of the items that you want to pass on) and then refer to the “group” in the script. Apparently - unless somebody tells us how to do it - you cannot really pass on multiple entity_id variables.

Hopefully somebody else has found a solution and can help?

That’s exactly what I started doing.

I spent a few hours trying to find a hack, like passing the text as a string then converting it into an array in the script, passing an array, passing the list directly, etc. - couldn’t get anything to work. At some point it just becomes easier and less time consuming to do the clunky workaround than figure out a solution.

{% for state in states. media_player %}{{ state.entity_id }},{% endfor %}

I did get an error when trying to use that:

Invalid service data for media_player.volume_set: Entity ID is an invalid entity id for dictionary value @ data[‘entity_id’]. Got ‘media_player.bedroom_1,\n\n media_player.dining_room_1,\n\n media_player.dining_room_2,\n\n media_player.foyer,\n\n media_player.guest_bathroom,\n\n media_player.kitchen_1,\n\n media_player.kitchen_2,\n\n media_player.living_room_1,\n\n media_player.living_room_2,\n\n media_player.shower,’

But the code does work when I am testing it out in the script editor. I think I’ll stick to my sonos group for the volume setting just because if I add another media_player to my system that isn’t sonos I wouldn’t want to run the command on it; the group gives me a little more control.

1 Like

I know this is a little dated, but here’s how you can pass the standard entity list to a script, and then have the script correctly act on that list. This method works with one or more entries in a standard YAML list, such as:

 light_name: 
   - light.training_room
   - light.basement_bathroom

The script wants a simple list, like:

list_of_entities = light.training_room,light.basement_bathroom

Instead, what gets passed is:

list_of_entities = ['light.training_room', 'light.basement_bathroom']

So you can’t use the passed list as a direct, 1-to-1 drop-in for the entity_id list in the script - bummer!

The solution is to recreate the list in the proper format using a Jinja for loop. For example, here’s the entity_id list I want to pass from the script service call in the action portion of an automation to the actual script:

  - service: script.dim_or_brighten_lights
    data:
      action: DIM
      light_name: 
        - light.training_room
        - light.basement_bathroom
      caller: Calling automation name (whatever that is) 

Then in the script, use the following (explanation follows):

dim_or_brighten_lights:
  alias: Dim or brighten lights
  description: 'Dim or brighten lights'
  fields:
    action:
      description: 'Action to execute DIM (100) or BRIGHTEN (255)'
      example: 'DIM or BRIGHTEN'
    light_name:
      description: 'Name of light to act on'
      example: 'light.living_room'
    caller:
      description: 'Name of calling script/automation'
      example: 'shut_down_roku_tv'
  sequence:
    - service: system_log.write
      data_template:
        message: "SCRIPT {{caller}}>dim_or_brighten_lights: Action: {{action}} on {{light_name}}"
        level: warning
    - service: light.turn_on
      data_template:
        entity_id: >
            {% for e in light_name %}
              {% if loop.first %}{% else %}, {% endif %}
            {{ e }}
            {% endfor %}
        brightness: "{% if 'DIM' in action or 'dim' in action %} 100 {% else %} 255 {% endif %}"
        kelvin: "{% if 'DIM' in action or 'dim' in action %} 2000 {% else %} 4000 {% endif %}"

In this script, I have three fields:

  • action: either DIM or BRIGHTEN
  • light_name: a single entity_id or a list of entity ids (refer to the light_name in the action part of the automation
  • caller: I always use this so I know what automation is calling the script (my whole idea of using a script is that it is reusable code that can be called from more than one place). I then use that in a system_log.write service to write to the home-assistant.log. If something goes wrong, I can look at the home-assistant.log file to trace down the error

Under sequence, I first write a message to the log. {{caller}} is the name of the automation that called the script. I also write out the action and the entity id list ({{light_name}})

Finally, we get to the good part. Our goal is to convert the string that is passed by the automation to a list WITHOUT single quotes that can be used in the script.

As stated above, what we need is a list like this:

entity_id: light.training_room,light.basement_bathroom

Under the data_template, I include a for loop. I’ll describe each line because if you’re not used to Jinja syntax (and I’m far from an expert!), it can be a little intimidating:

{% for e in light_name %}

This line cycles through each name in the list of entities you passed to the script. The variable e takes on the value of each item in turn in the light_name list that was passed from the automation. In the case of this example, it first takes on the name light.training_room. Then the second time through, it takes on the name light.basement_bathroom.

{% if loop.first %}{% else %}, {% endif %}

Here’s what’s happening. The {% if loop.first %} checks if this is the first time through the list light_name. If so, then move on to the next line, which is {{ e }}. After the first pass, our entity_id parameter is:

entity_id: light.training_room

Now, each subsequent time through the loop, we need to add a comma as a separator in the list. That’s what the {% else %} statement does.

After going through the second time, entity_id is first changed to:

entity_id: light.training_room,  #<<< Note the comma has been added by the if/else/endif logic

Then the next entity is added so that we finally have:

entity_id: light.training_room,light.basement_bathroom

Now that we’re at the end of the passed list, the for loop stops with the {% endfor %} statement. The entity_id for the script is now complete and correct.

Hope this helps some others. I struggled getting to this point for a while. By adding this feature to my scripts, I was able to clean up a few things (calling the script for each entity id, creating unnecessary groups). As an aside, I use this script to DIM or BRIGHTEN any lights in the house. That way it’s always consistent, which creates a feeling of control for the others in the home.

2 Likes

You can replace this:

    - service: light.turn_on
      data_template:
        entity_id: >
            {% for e in light_name %}
              {% if loop.first %}{% else %}, {% endif %}
            {{ e }}
            {% endfor %}

with this:

    - service: light.turn_on
      data_template:
        entity_id: "{{ light_name | join(', ') }}"

It converts the received python list:

['light.first', 'light.second']

to a simple comma-delimited list of entity names:

light.first, light.second

I tested it using this simple script (derived from yours):

  test_1:
    alias: Test 1
    description: 'test 1'
    fields:
      light_name:
        description: 'Name of light to act on'
        example: 'light.living_room'
    sequence:
      - service: light.turn_on
        data_template:
          entity_id: "{{ light_name | join(', ') }}"

and called it like this:

Screenshot from 2020-06-05 13-45-22

Running the script turned on all three lights.

3 Likes

That’s even simpler!! Thanks for the update - I’m changing mine to use the join command. Fewer lines, and still very readable. Again, thanks!

Hello, @123 Taras - Another question for you. I saw in an older post https://community.home-assistant.io/t/a-list-generated-by-a-template-is-it-possible-help/111239/25 where you and others were trying to determine if the automation can call the script from a template, but with a list of entity ids. The conclusion seemed to be that it can’t be done, and that one would have to use a Python script instead.

Do you know if this is possible now? What I’d like to do is consolidate some automations using templates. The templates would determine which entities to pass to the script. I can get it to work fine with one entity, but not with more than one. As an example, I have a number of automations that trigger when a specific timer expires. I’d love to use just one automation to manage what actions to take when a timer expires, such as:

- alias: Test timer expired manager
  trigger:
    platform: event
    event_type: timer.finished
    event_data:
      entity_id: 
        - timer.timer_1
        - timer.timer_2
  action:
  - service: system_log.write
    data_template:
      message: >
        AUTO Timer manager: Timer expired: {{trigger.entity_id}} 
      level: warning
  - service: script.test_script
    data_template:
      caller: Timer manager
      action: DIM
      light_name: > 
        {% if    'timer_1' in trigger.entity_id %} light.light_1, light.light_2
        {% elif  'timer_2' in trigger.entity_if %} light.light_3, light.light_4
        {% endif %}

But this errors out in the script when I try to use the passed parameter light_name as the entity list in the script. This is the same script I described above. I’m pretty sure the passed parameter is a string, and I can’t seem to figure out how to convert it to a list that the script can use.

Thanks for any advice!

All the best.

No. The discussion was about the value produced by a template and the answer is it’s a string value (and it hasn’t changed since the time of that discussion).

It is a string. Effectively, it looks like this:

{% set light_name = 'light.light_1, light.light_2' %}

No further conversion is required because it’s already in the form that is acceptable to entity_id when used with light.turn_on or light.turn_off. However, if you do have a need to convert it to a list, just split the string like this:

{% set x = light_name.split(', ') %}

Thank you, @123. I’ll give this a shot. I was WAAAYYY over thinking this! I can’t remember now how I went down this rabbit hole - probably an unrelated error that made me think it was the entity_id :slight_smile:

What would be the correct syntax to supply the following remote commands as a template variable?

  sequence:
  - service: remote.send_command
    data:
      command: [down,down,select] # This works
    entity_id: remote.apple_tv

I’ve tried:

  sequence:
  - variables:
      remote_codes: [down,down,select]
  - service: remote.send_command
    data_template:
      command: "{{ remote_codes }}"
    entity_id: remote.apple_tv

and the following variations:

"{{ remote_codes | join(', ')}}"

"[{{ remote_codes | join(', ')}}]"

The output of a template is always a string. You are trying to use a template to produce a list and that’s not possible.

Thank you, Taras. That makes a lot of sense, now.
Your response put me on the right track.
For anyone else looking to do something similar with the remote.send_command, the following worked for me:

- choose:
    - conditions: '{{ media_choice == ''Plex'' }}'
      sequence:
      - service: remote.send_command
        data:
          command: [down,down,select]
        entity_id: remote.apple_tv
    - conditions: '{{ media_choice == ''Prime'' }}' 
  ...

this should be marked as the response
great answer - just helped me, thx