Why do my Timers run twice? Help needed please

Ok, I’m not having much luck with timers at the moment (for example) but please indulge me because this is driving me bonkers…

I have some timers which I want to run in succession one after the other. The scripts perform a small loop passing zone numbers.

This all works beautifully except that every timer runs twice, except the first one which is obviously a clue, but I cannot find the answer.

Please have a look at this…

The following code is a complete package. If you want you can test it for yourself.

Thanks in advance…



script:
  #=== START HERE
  irrigation_test:
    sequence:
      - service: script.irrigation_test_irrigate_a_zone
        data:
          zone: zone1


  #=== IRRIGATE A ZONE
  irrigation_test_irrigate_a_zone:
    sequence:
      - service: homeassistant.turn_off
        entity_id: script.irrigation_test_start_zone_timer

      - service: script.irrigation_test_start_zone_timer
        data_template:
          zone: "{{ zone }}"


  #=== START A TIMER
  irrigation_test_start_zone_timer:
    sequence:
      #=== Start the timer
      - service: timer.start
        data_template:
          entity_id: >
            timer.irrigation_test_{{ zone }}_timer
          duration: >
            {% set minutes = states('input_number.irrigation_test_cycle1_' ~ zone ~ '_duration') | float %}
            {{ minutes | int }}

      #=== Wait for timer to end
      - wait_template: >
          {% set entity = 'timer.irrigation_test_' ~ zone ~ '_timer' %}
          {{ is_state(entity, 'idle') }}

      #=== Increment zone number and restart, or end
      - service_template: >
          {% if (zone[-1] | int) == 4 | int %}
            script.irrigation_test_cycle_end
          {% else %}
            script.irrigation_test_irrigate_a_zone
          {% endif %}
        data_template:
          zone: >
            {% set next_zone = (zone[-1] | int) + 1 %}
            zone{{ next_zone | string }}

  
  #=== END PROCESSING
  irrigation_test_cycle_end:
    sequence:
      - delay: "00:00:01"

#========
timer:
  irrigation_test_zone1_timer:
  irrigation_test_zone2_timer:
  irrigation_test_zone3_timer:
  irrigation_test_zone4_timer:

input_number:
  irrigation_test_cycle1_zone1_duration:
    min: 0
    max: 60 
  irrigation_test_cycle1_zone2_duration:
    min: 0
    max: 60    
  irrigation_test_cycle1_zone3_duration:
    min: 0
    max: 60    
  irrigation_test_cycle1_zone4_duration:
    min: 0
    max: 60    

input_boolean:
  irrigation_test_cycle1_zone1_skip:
  irrigation_test_cycle1_zone2_skip:
  irrigation_test_cycle1_zone3_skip:
  irrigation_test_cycle1_zone4_skip:



I edited the above post to make it easier to read and the code is now briefer and a complete package which can be tested.

I just tested your package and I think I figured out the problem.

It seems to be a “race” condition in that the “zonex” isn’t getting updated in the state machine before the next iteration of the timer gets started. So it still thinks it’s on the last zone until the next cycle thru.

If I add a 1 second delay into the “irrigation_test_irrigate_a_zone” script everything works fine:

  #=== IRRIGATE A ZONE
  irrigation_test_irrigate_a_zone:
    sequence:
      - service: homeassistant.turn_off
        entity_id: script.irrigation_test_start_zone_timer
      - delay: '00:00:01'
      - service: script.irrigation_test_start_zone_timer
        data_template:
          zone: "{{ zone }}"
2 Likes

I’m not an expert, but many things in HA are async. Could it be that turn_off does not reach the state machine when service: script.irrigation_test_start_zone_timer is called? So using that off/on approach defeats its purpose as it’s incorrect considering HA’s internals?

I’m (obviously…:wink:) no expert here either so it could be.

TBH, I didn’t really know what the point was of turning off the other script there before restarting it again immediately.

I just kind of assumed it was put in if for some reason they wanted to use an automation to restart the whole sequence beginning at zone1 even if the entire 4 iterations of loop hadn’t completed.

If that’s the reason then my added delay allows both functionalities to work.

Of course, there may be a better way to do it but I doubt it could be done any more simply. :laughing:

EDIT to add:

Many things in HA are async but as far as I understand it automation actions (and hence scripts since they are the same thing) are done sequentially.

so the updating of the state machine is asynchronous but the stopping and starting of the various scripts are sequential. So that might be the issue I was describing above.

or maybe I’m completely wrong! :stuck_out_tongue:

1 Like

because there can be only one script running so if you try to run a running script… well, you will not re-start it as you might restart a timer, for example. that’s my understanding.

I was talking about the reason of the phenomenon, not about the solution :wink:

1 Like

But the script that is doing the looping (irrigation_test_start_zone_timer) has already completed and it has called the other control script (irrigation_test_irrigate_a_zone) before the control script calls the looping script again.

Wow! this is confusing! :crazy_face:

But the bottom line is that there should be no reason to stop the looping script since it has already completed before the control script runs it again on the following zones.

Unless it’s just to make sure that the looping script is registered as not running before it’s called again? But I think the delay should solve that issue too.

that’s why I said this to the TS but he does not believe me :wink:

most likely. I said that, too.
didn’t dig deeper as I can see no software error here :wink:

Why are you using timers? So much easier with templated delays, and you aren’t relying on a timer being reset. No race condition. No wait statement. Simple delay that will fire your crap after the delay is reached. This is how we all used to do this stuff before timers existed. Ever since they were added, it just makes automations 10x more complicated. I avoid them.

  irrigation_test_start_zone_timer:
    sequence:
      #=== Start the timer
      - delay: >
          {% set minutes = states('input_number.irrigation_test_cycle1_' ~ zone ~ '_duration') | float }}
          00:{{ minutes }}:00
      - service_template: >
          {% if (zone[-1] | int) == 4 | int %}
            script.irrigation_test_cycle_end
          {% else %}
            script.irrigation_test_irrigate_a_zone
          {% endif %}
        data_template:
          zone: >
            {% set next_zone = (zone[-1] | int) + 1 %}
            zone{{ next_zone | string }}

EDIT: ANd it gets rid of all these unnecessary entities. Decluttering your listeners and states page. Just my $0.02.

EDIT2: Just gotta point out, your automation is already set up to use this exact script. This should behave the same way as your timer, without the race condition.

EDIT23902424234: get rid of this

      - service: homeassistant.turn_off

and replace it with

      - service: script.turn_off

@AhmadK and @finity
Thanks for that, I don’t have time to read it all properly now but I will and I certainly appreciate the time you took to respond (and illuminate).

@petro
The main reason I like timers in this case is that they give a nice countdown in the frontend but you’re probably right to suggest delay. Timers in my experience are a lot of hassle and I couldl be wrong but don’t think they have been implemented very well in HA.

Anyway, thanks all of you…

set and start the timer but use the delay

1 Like

Yeah, thought of that, thanks.

Sometimes timers make possible things that won’t work if we use non-timer approach.
Afaik we’ll have problems triggering automation that uses a delay and is already running but we can avoid it using timers (can’t remember anything more convincing atm).

I think generally use of timers is not as straightforward as traditional procedural way, it’s not natural to our brain.

Not if you put the delay in your script and cancel (turn_off) the script before starting the script.

And if I use delay in an automation?

Ok… Everything that has been said is all good and well and valuable information but strangely it hasn’t solved my question…

I still get my timer running twice. I don’t wait for it to finish, I use a templated delay which happens immediately after the timer is started.

The sequence should be:

  • Start Timer 1
  • Delay for the same time Timer 1 runs for
  • Delay for 3 seconds
  • Loop and restart the process
  • Start Timer 2
  • Delay for the same time Timer 2 runs for
  • Delay for 3 seconds
  • Loop and restart the process

What actually happens is:

  • Start Timer 1
  • Delay for the same time the timer runs for
  • Delay for 3 seconds
  • Loop and restart the process
  • Start Timer 2
  • Delay for the same time the timer runs for
  • Start Timer 2
  • Delay for the same time the timer runs for
  • Delay for 3 seconds
  • Loop and restart the process

Note

  • The first time through it works as expected, as I said in the OP, a clue but I’m still stumped.
  • The delay for 3 seconds after each timer ends only occurs after the timer runs for the second time.

Please can someone try this on their setup.
Here is the code as a complete package made as brief as I can and I include a Lovelace card too for easy testing.

Thank you.

script:
  #=== START HERE
  irrigation_test:
    sequence:
      - service: script.irrigation_test_irrigate_a_zone
        data_template:
          zone: >
            zone1


  #=== IRRIGATE A ZONE
  irrigation_test_irrigate_a_zone:
    sequence:
      - service: script.turn_off
        entity_id: script.irrigation_test_start_zone

      - service: script.irrigation_test_start_zone
        data_template:
          zone: "{{ zone }}"


  #=== START A ZONE
  irrigation_test_start_zone:
    sequence:

      #=== Start the timer
      - service: timer.start
        data_template:
          entity_id: >
            timer.irrigation_test_{{ zone }}_timer
          duration: >
            {% set duration = states('input_number.irrigation_test_cycle1_' ~ zone ~ '_duration') | float %}
            {{ duration | round() }}

      #=== Set delay
      - delay: 
          seconds: >
            {% set duration = states('input_number.irrigation_test_cycle1_' ~ zone ~ '_duration') | float %}
            {{ duration | round() }}

      #=== Pause between zones
      - delay: "00:00:03"

      #=== Increment zone number and restart, or end
      - service_template: >
          {% if zone[-1] | int == states('input_number.irrigation_test_number_of_zones') | int %}
            script.irrigation_test_cycle_end
          {% else %}
            script.irrigation_test_irrigate_a_zone
          {% endif %}
        data_template:
          cycle: >
            {{ cycle }} 
          zone: >
            {% set next_zone = (zone[-1] | int) + 1 %}
            zone{{ next_zone | string }}


  #=== END PROCESSING
  irrigation_test_cycle_end:
    sequence:
      - delay: "00:00:01"



#================
timer:
  irrigation_test_zone1_timer:
  irrigation_test_zone2_timer:
  irrigation_test_zone3_timer:
  irrigation_test_zone4_timer:

input_number:
  irrigation_test_number_of_zones:
    min: 0
    max: 10
    initial: 4

  irrigation_test_cycle1_zone1_duration:
    min: 0
    max: 10
    
  irrigation_test_cycle1_zone2_duration:
    min: 0
    max: 10
    
  irrigation_test_cycle1_zone3_duration:
    min: 0
    max: 10
    
  irrigation_test_cycle1_zone4_duration:
    min: 0
    max: 10

input_boolean:
  irrigation_test_cycle1_zone1_skip:
  irrigation_test_cycle1_zone2_skip:
  irrigation_test_cycle1_zone3_skip:
  irrigation_test_cycle1_zone4_skip:

Lovelace View

title: Garden
path: garden
icon: mdi:flower

cards:
  - type: entities
    title: Testing
    show_header_toggle: false
    entities:
      - entity: script.irrigation_test
      - entity: input_number.irrigation_test_cycle1_zone1_duration
        name: Zone 1 Duration
      - entity: timer.irrigation_test_zone1_timer
      - entity: input_number.irrigation_test_cycle1_zone2_duration
        name: Zone 2 Duration
      - entity: timer.irrigation_test_zone2_timer
      - entity: input_number.irrigation_test_cycle1_zone3_duration
        name: Zone 3 Duration
      - entity: timer.irrigation_test_zone3_timer
      - entity: input_number.irrigation_test_cycle1_zone4_duration
        name: Zone 4 Duration
      - entity: timer.irrigation_test_zone4_timer

Move the action section to a script and run the script from the automation, as per @petro 's suggestion.

I’ve not seen a scenario where a timer is needed for anything, and they’re very unreliable. for: , delay: and wait_template: cater for everything in a much more stable way.

I see the’ visual countdown’ argument, but I very rarely look at my interface (and I think if you do you’re not automating enough) so it’s pretty moot point.

1 Like

Have you tried a delay between the service calls in script.irrigation_test_irrigate_a_zone ?

I completely agree except there are cases when the automation is kind of secondary. Watering my grass is nice to have starting automatically but I’d like to be able to see when it is going to finish. Especially if the duration is variable based on weather conditions.

But in principle I agree.

And…

I’m on it and will report back

EDIT: Actually I have just remembered @finity suggested that right at the beginning :blush: :blush: :blush:

EDIT: 2 Yeah that works. :woozy_face: My profound apologies to @finity for apparently ignoring you and to @anon43302295 for giving me a kick up the jacksey.

Hopefully we I can now move on…

1 Like

Except…
I know I’m probably pushing my luck here but,

Is there a good technical reason to use the specific turn_off instead of the generic homeasistant one?