Repeating a service (or script)?

Hello,
There is an option to run a service (or script) in loop ?
Means, can the service repeat 10 times (without a delay) ?

The following is my automation that play a shot sound, and I wants to repeat this sound for 10 times.

- alias: 'The house was flooded with water notification'
  trigger:
    platform: state
    entity_id:
      - binary_sensor.storage_water_sensor
    from: 'off'
    to: 'on'
  action:
    - service: media_player.play_media
      data:
        entity_id: media_player.alex_s_echo_dot
        media_content_type: sound
        media_content_id: amzn_sfx_church_bell_1x_02

Please advice.

Thanks.

No, there’s no posibility to do a loop. Couldn’t you just create a single media file that loops the sound 10 times and then just play the media file?

I wish I could do that.
Because I’m using Alexa and not Google, I can’t play a local MP3.

To answer your question about a service in a loop:

You’ll have to do 2 scripts sadly. A script cannot run multiple times, so it can’t call itself recursively.

Also, I add wait_templates to be sure the recursive scripts are actually done before continuing.


# Scripts
notify_loop:
  sequence:
    # Wait for the loop script to be completely done, then call it again. 
    - wait_template: "{{ is_state('script.notify', 'off' }}"
    - service: script.notify


notify:
  sequence:
    - service: #thing_I_want_to_loop
    - delay: "00:00:10"
    # Wait until the notify_loop script is completely done. Can't call it
    # again if it's still running. It should be done by now, but that
    # depends on how long your delay is...
    - wait_template: "{{ is_state('script.notify_loop', 'off') }}"
    - service: script.notify_loop

To solve it the correct way, simply create an automation that does nothing but repeat every X seconds/minutes/hours. Then turn this automation on/off.

- alias: Looping alarm
  trigger:
    platform: time_pattern
    # With the / in front, will play when the minutes are evenly divisible by this number. 
    # So, every 1 minute in this case.
    minutes: "/1"
  action:
    - service: media_player.play_media
      data:
        entity_id: media_player.alex_s_echo_dot
        media_content_type: sound
        media_content_id: amzn_sfx_church_bell_1x_02
  

- alias: 'The house was flooded with water notification'
  trigger:
    platform: state
    entity_id:
      - binary_sensor.storage_water_sensor
    from: 'off'
    to: 'on'
  action:
    # Turn on the automation that just loops every X minutes.
    - service: homeassistant.turn_on
      entity_id: automation.looping_alarm

Then turn that looping automation off by whatever method you choose.

I thought this might work:

  action:
    - service_template >
        {% for i in range(0, 9) %}
          media_player.play_media
          data:
            entity_id: media_player.alex_s_echo_dot
            media_content_type: sound
            media_content_id: amzn_sfx_church_bell_1x_02
        {% endfor %}

And technically it will loop, but without a wait template in there to prevent the service being called while still playing the previous iteration it wont work. And there is no way to shoehorn in a wait template.

Two scripts is the way to go.

This works??? I need to test this immediately

Ah okay, didn’t know that. (One reason more for me to not have Alexa xD)

** UNTESTED **

As said, without a wait template it’s going to try and play the next iteration over the top of the last. But it should work for other things.

This would just generate a gigantic string.

action:
  - service_template:
    "media_player.play_media
     data:
       <data>
     media_player.play_media
     data:
       <data>
     ...
     ...
    "

Then home assistant would complain about the lack of serivce and data fields :frowning:

2 Likes

I just realized now that this will not work, a template will always only return a single string.

1 Like

Ah, so it would.

It’s a shame you can’t have “naked” templates, i.e. outside an entity or service.

There’s a feature request here that should be voted for if you haven’t already and want easy loops:

Easily done with the python_script integration.

church_bells.py

service_data = {'entity_id':'media_player.alex_s_echo_dot', 'media_content_type':'sound', 'media_content_id':'amzn_sfx_church_bell_1x_02'}
for i in range(10):
  hass.services.call('media_player', 'play_media', service_data, False)
  time.sleep(2)

Adjust sleep(2) to the number of seconds needed to allow for the playing of the media content.

Automation simply becomes this:

- alias: 'The house was flooded with water notification'
  trigger:
    platform: state
    entity_id:
      - binary_sensor.storage_water_sensor
    from: 'off'
    to: 'on'
  action:
    service: python_script.church_bells
1 Like

Instead of using time.sleep can you use a wait template to wait for the media player to finish playing?

1 Like

Thanks.
looks good,
I’ll try it.

Thanks,
This is working fine for me.
I just need to think how to turn it off after 20 iteration.

If there is a way to do that, I don’t know how. The python_script integration supports python code and wait_template is something specific to Jinja2 templating in Home Assistant.

Here’s my concept of creating loops.
I replaced your binary_sensor.storage_water_sensor with an input_boolean as it’s easier to test and also used persistent_notification.create service to get a feedback.
The number of loops (10) is hardcoded but it’s easy to change the code so it would take it from say, input_number and that way you’ll have a configurable loop :wink:

I put it in a package:

loop:
  counter:
    loop:

  input_boolean:
    loop:

  automation:
  - alias: 'test loop'
    trigger:
      - platform: state
        entity_id: input_boolean.loop
        from: 'off'
        to: 'on'
      - platform: event
        event_type: automation_loop
    action:
      - service: persistent_notification.create
        data_template:
          message: >
            {{ states('counter.loop') }}
      - service_template: >
          counter.{{ 'increment' if states('counter.loop')|int < (10-1) else 'reset' }}
        data:
          entity_id: counter.loop
      - condition: template
        value_template: >
          {{ 0 < states('counter.loop')|int < 10 }}
      - event: automation_loop

not sure if this helps, but it works:

  play_tune:
    alias: Play tune
    sequence:
      - service: script.tune
      - delay:
          seconds: >
           {{state_attr(states('sensor.tune_player'),'media_duration')|int}}
      - condition: state
        entity_id: input_boolean.loop_tune
        state: 'on'
#      - condition: template
#        value_template: >
#          {{is_state('input_boolean.loop_tune','on')}}
      - service: script.play_tune_loop

  play_tune_loop:
    alias: Play tune loop
    sequence:
      - service: script.tune
      - delay:
          seconds: >
           {{state_attr(states('sensor.tune_player'),'media_duration')|int}}
      - condition: state
        entity_id: input_boolean.loop_tune
        state: 'on'
#      - condition: template
#        value_template: >
#          {{is_state('input_boolean.loop_tune','on')}}
      - service: script.play_tune

@123

There is an option to turn off the python_script in a middle (let’s say that the for loop is already running),
From the automation that called to this python_script ?

Something like:

- service: homeassistant.turn_off
  entity_id: python_script.church_bells

Regards,
Alex.