Looping a repeated service call

Hi,

Understand this could be asked before but i couldn’t find a solutions through all the searches.
Assume I wish to set the 3 blinds each at specific position using a string such as ‘30,60,80’, how can i convert the following e fix service calls into a loop script / automation:-

action:
  - service: cover.set_cover_position
    target:
      entity_id: cover.window_covering_device_38
    data:
      position: 30
    - service: cover.set_cover_position
    target:
      entity_id: cover.window_covering_device_39
    data:
      position: 60
  - service: cover.set_cover_position
    target:
      entity_id: cover.window_covering_device_41
    data:
      position: 80

can i make it into something like this ? Apology for creating an untested & probably messy scripts as I’m a Jinja or template beginners.

group:
  sr_blinds:
    name: study room blinds
     entities:
        - cover.window_covering_device_38
        - cover.window_covering_device_39
        - cover.window_covering_device_41
#
- service_template: >
  {% set blind_pos = "30,60,80" %}
  {% set bpos = blind_pos.split(',') %}
  {% set blind_entity =  expand('group.sr_blinds') | map(attribute='object_id') | list %}

  {% for i in range(bpos|length) %}
    cover.set_cover_position
    target:
      entity_id: {{ blind_entity[i] }}
    data:
      position: {{ bpos[i] }}
  {% endfor %} 
1 Like

Is your intention to pass this string to a script as a variable?

Is there a particular reason why you do this:

  {% set blind_pos = "30,60,80" %}
  {% set bpos = blind_pos.split(',') %}

Instead of this?

  {% set bpos = [30,60,80] %}

Yes. I’m thinking of passing to the script either using a string variables or input_text. My intention is to have different modes defined in an input_select and when a particular input_select is selected, it will pass a particular set of blind positions to the script.

Create a script with a Counted Repeat.

The following script is passed a variable named blind_pos containing a comma-delimited string like 30,60,80.

script:
  set_blinds:
    sequence:
      - variables:
          position: "{{ blind_pos.split(',') }}"
          covers: "{{ state_attr('group.sr_blinds', 'entity_id') }}"
          covers_qty: "{{ covers | count }}"
      - condition: "{{ position | count == covers_qty }}"
      - repeat:
          count: "{{ covers_qty }}"
          sequence:
            - service: cover.set_cover_position
              target:
                entity_id: "{{ covers[repeat.index - 1] }}"
              data:
                position: "{{ position[repeat.index - 1] }}"

Example of an automation’s action calling the script:

  action:
    - service: script.set_blinds
      data:
        blinds_pos: "30,60,80"

Taras, thanks for the great help. I tested on the developer service tools and it works perfectly as what i wanted… one more thing to trouble you, below is the calling automation which doesn’t seems to activate the different input select mode as required, can you spot any mistakes there ?


- alias: Study Room Blind Scenes
  trigger:
    platform: state
    entity_id: input_select.sr_blind_scn
  action:
    - service: script.set_blinds
      data_template:
        blinds_pos: >
          {% if states('input_select.sr_blind_scn') == 'night' %}
            '30,30,30'
          {% elif states('input_select.sr_blind_scn') == 'afternoon' %}
            '10,10,10'
          {% elif states('input_select.sr_blind_scn') == 'morning' %}
            '65,65,65'
          {% endif %}

I suggest the script use a dictionary called blinds. The dictionary’s keys corresponds to the input_select’s options. Each key’s value is a list containing the blind positions.

So if the input_select’s value changes to ‘afternoon’, its corresponding value, [10,10,10] is assigned to the script variable blind_pos. If for some reson the input_select’s value is not found in the blinds dictionary, it will assign [50,50,50] to blind_pos (as a way for you to detect an error has occurred).

- alias: Study Room Blind Scenes
  trigger:
    platform: state
    entity_id: input_select.sr_blind_scn
  action:
    - variables:
        blinds:
          night: [30,30,30]
          afternoon: [10,10,10]
          morning: [65,65,65]
    - service: script.set_blinds
      data:
        blinds_pos: "{{ blinds.get(trigger.to_state.state, [50,50,50]) }}"

Because you can pass a list value to the script, instead of a string, the script doesn’t need to convert it to a list so we can eliminate one line of code.

script:
  set_blinds:
    sequence:
      - variables:
          covers: "{{ state_attr('group.sr_blinds', 'entity_id') }}"
          covers_qty: "{{ covers | count }}"
      - condition: "{{ blinds_pos | count == covers_qty }}"
      - repeat:
          count: "{{ covers_qty }}"
          sequence:
            - service: cover.set_cover_position
              target:
                entity_id: "{{ covers[repeat.index - 1] }}"
              data:
                position: "{{ blinds_pos[repeat.index - 1] }}"

BTW, service_template and data_template were deprecated in favor of service and data many versions ago.

1 Like

Hi Taras,

Thanks so much… is been a great help from you, always.

Just curious, i tested out my old ‘if…elif…endif’ templates using list instead but still not working… I have another routine i am using this template structure and it is not working either… wonder if my syntax structure has any issue on that ?

Also, i have tried your syntax using string ( eg. ‘50,30,10’ ) instead of list, it doesn’t work either… does that means dictionary value must be list ?

Anyway, the whole procedure works perfectly using your syntax and i learn something new about dictionary and how to use ‘variables’ in the script… thanks also on highlighting the deprecated syntax.

Several versions ago, “native types” was implemented. That means a value’s type was no longer limited to string but could be list, integer, boolean, etc. This made passing a script variable more flexible because instead of the type being limited to just string it could now be another type.

Home Assistant determines a value’s type from its appearance. So 23.5 is handled as a float, 45 is int ,[23.5, 45] is a list but I believe it can also interpret 23.5, 45 as a list (or tuple), not a string, and perhaps that’s the issue that’s affecting your version of the template.

Got it… thanks again for your time in helping.

Can we put in a feature request for what kwaek suggested (service or other repeated calls within loops/conditionals). I know it’s not the “HA way of doing things” but it’s much more intuitive, shorter, readable, and self-contained.

Home Assistant processes YAML first then Jinja2 second. That’s why the following can never work because it attempts to use Jinja2 to generate YAML.

  {% for i in range(bpos|length) %}
    cover.set_cover_position
    target:
      entity_id: {{ blind_entity[i] }}
    data:
      position: {{ bpos[i] }}
  {% endfor %} 

Sounds like a job for recursive interpreter.

You can either implement your idea, via a Pull Request in the Core repository, or submit a Feature Request.