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 }}"
I’m not an expert, but many things in HA are async. Could it be that turn_offdoes 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…) 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.
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.
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
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!
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.
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.
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.
@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.
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.
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:
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.
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
EDIT: 2 Yeah that works. My profound apologies to @finity for apparently ignoring you and to @anon43302295 for giving me a kick up the jacksey.
why use a generic when you have the actual turn_off? I haven’t gone through the code so I can’t be certain it will help better. All I can say is that I would trust script.turn_off more than homeassistant.turn_off based on helping people in the past.
Not to mention, I just found a way to get visual countdowns in lovelace using just a single timestamp. It’s complicated, I plan on posting about it in the future.