Repeated tasks under the action part of an automation seem to be a quite common, although complicated, task to script. An example would be to blink a light on and off or to switch between two colors of a light x times. The most basic way of doing this would be to copy the on/off parts x times in the automation. A more advanced way seem to be to make two scripts, and have them call the other script at the end of each of these. Both of these seem to be quite cumbersome solutions to an often occurring task in HA.
Would it be possible to add âloopâ as a method in an automation/script? For example like this to repeat the corresponding tasks five times:
I like the idea, the only thing that youâd need in addition to the number of loops is the time element.
So probably something like:
action:
service: loop.loop
data:
loops: 5
delay_before: 5 # 5 sec to wait before running each loop, should be optional
delay_after: 10 # 10 sec to wait after running each loop, should be optional
actions:
- service: light.turn_on
entity_id: light.livingroom
- service: light.turn_off
entity_id: light.livingroom
How about a stop_if: that could be used with or instead of a loop count and uses a similar syntax to automation conditions?
eidt: nevermind, I think thats what oyu meant by the condition
The âdelay_beforeâ and âdelay_afterâ can easily be handled by putting in the existing âdelay:â command in line with the services in the actions part, and is therefore not necessary:
Thatâs not been my experience and Iâve been automating my home since 2006. In fact, if you need to loop something 100 times, you might want to rethink what youâre attempting to achieve.
The following example has potentially negative implications for lighting systems based on wireless mesh technologies. Thereâs a minimum time required to propagate the command to the device and receive its acknowledgement. The loop is processed as fast as the host system can execute it. For this application, allowing the loopâs timing to be based on the hostâs execution speed is a bad design pattern.
A superior, deterministic solution, is an event-based design pattern. For example, use a timer to control each iteration of the repeated action (timer duration is 1 second). When the timer starts, it toggles the light, when it finishes it increments a counter. The counter restarts the timer but only if the count is less than the maximum number of iterations. Regardless of the host systemâs execution speed, this solution will behave the same way (and provides the targeted device a 1-second window to receive and acknowledge its state-change before the next command arrives).
If you think this is more âcumbersomeâ than a for-loop, shortcuts always seem easier ⊠at first.
PS
Donât forget that delay is effectively an in-line timer that suspends execution of the action or script. However, that action/script is still in the process of being executed (even if it is currently suspended). Because you canât call the same script/action while it is being executed, this can have implications on how and when the script/action may be called.
If you loop something many times, you should also add the means to cancel it. Imagine having a loop: 100 that you want to abort. This is easily done with the timer-based example I provided (set the counter to its maximum value or just cancel the timer). With the proposed system, I see the suggestion stop_if which suggests it depends on another entityâs state (which now becomes a multi-part solution, like the timer-based one).
The «loop: 100» was an example to highlight that a condition to stop the loop based on external input could be handy, just as you point out in your PS. I can not think of a reason to set up a loop with 100 iterations myself, other than perhaps to flash the lights continuously in case of a fire or as an intruder alert.
The action part of the example was just ment to illustrate how such a loop-function could be formatted in a yaml setting, just like a âlorem ipsumâ text is used in the design world, and is not intended as an example to follow without adding the appropriate delays to handle the need for âtime required to propagate the command to the device and receive its acknowledgementâ, as you put it.
I am not saying that a loop is the way to go for every need in automations, but yes I really do think that setting up a system with timers (which after my understanding calls for a restart of HA while a reload of just the automation.yaml is much faster) and counters spread out on several scripts is overly âcumbersomeâ when all you really want to achieve is flash a lamp five times. Today I do this by copying the action five times in the automation script (which after my opinion still is a lot easier and transparent than handling it with a timer and counter), but it does lead to an unnecessarily long and bloated yaml-file. Hence the need for a loop function.
In one breath you explain âflashing lightsâ was just lorem ipsum ⊠and then you use the âflashing lightsâ example, yet again, to justify the use of looping:
when all you really want to achieve is flash a lamp five times
Seems like, other than âflashing lightsâ itâs hard to invent many other useful applications of repeating the same action over and over.
Well, the example as is, without implementing delays of any kind, was just lorem ipsum. At the same time the flashing of lights was the thing that got me thinking that a loop would be nice. But I am sure there are other applications for this function. Try taking away the for-next loop out of the toolbox of almost any programmer, and they will probably come after you with a vengeance. It is terribly useful in almost any kind of programming. I am quite sure that if we get the possibility to use loops in automations, people will find new use for it.
One of the great things for me with HA is the flexibility it provides. I can take the parts of HA that suit me, and leave the rest to those who have other needs. I donât see the point of limiting others just because I donât find all parts of HA equally interesting. I see this function as useful in MY use of HA, and I have come across many topics in here that tells me I am not alone. Weâll see if enough people vote for this function to make it tempting enough for the kind Gods of HA to implement it. And if they do, you donât have to use it, but you can if you will.
If you are sure, name three (excluding the beloved flashing lights).
Thatâs a strawman argument; for-loops exist elsewhere in Home Assistant and thereâs been no suggestion to remove them.
New uses for a bad design pattern. An uninterruptible loop controlling state-changes of external devices with no inherent consideration of propagation time (just the hope that some buffer somewhere will queue the flood of state-change requests).
I also think that this feature could be a great addition for out of the ordinary uses.
Thereâs something Iâm trying to do that would make use of a loop.
Iâm trying to control my A/C temperature with a broadlink RF transmitter.
Basically I have a script that sends a âtemperature upâ command and another for the âtemperature downâ.
Since I donât know the actual setpoint of the A/C, I used an input number that I synchronize witch each up/down calls. Letâs call it the actual A/C value.
I have another input_number that is the desired set point.
Iâm trying to find a way to make a script that would loop the up or down scripts until the A/C is at the desired set point.
Using a While(condition) loop for this would work great.
The For loop with a condition would also work, I could just put a large number for the loop counter and add a condition.
I managed to get this working with calling scripts but itâs not very efficient and creates a lots of useless scripts that should be only one really.
Also, using that method, I need to add delay: instructions before calling the other script otherwise it fails saying that the script is already in useâŠ
Those delays makes the temperature changes kinda very slowly. I think all that could be better with the use of loops.
You may want to use a simple python_script to perform the looping. A python_script offers you even more flexibility than a regular script.
Thereâs an example shown here:
The challenge was to send repeated commands (via a Broadlink device) to a fan in order to change its speed. The fan has 8 speed levels and one command is used to step through all the levels. The python_script calculates how many times the command must be sent to change from one speed to another. Then it proceeds to send the command the number of times it had calculated.
In this example, each iteration does two things:
Execute the broadlink.send service call (using the contents of the variable service_data).
Pause for a half-second.
for i in range(loop):
hass.services.call('broadlink', 'send', service_data, False)
time.sleep(0.5)
It will repeat those two commands the number of times specified by the variable loop.
Calling a python_script (from an automation) is similar to calling a regular script. Hereâs an example of calling a python_script and passing it a parameter called fan_speed:
The Developer Docs explain it in detail. However, I caution you that it is written for software developers so it is daunting for non-developers. My suggestion is to âlearn by exampleâ. Simply search the community forum for âpython_scriptâ and read the examples created by other people. Copy and paste and experiment.
Tip:
Thereâs no need to reload python_scripts like you do with scripts and automations. After you make a change to your python_script file, save it and it will be immediately available to Home Assistant. This makes it easier and faster to experiment and learn from oneâs mistakes (and successes).
If you want a sensible use-case: my hearing is not so good. If the fire alarm goes off, I want to ring my doorbells (I have 5 of them distributed through the house, one next to my bed).
Hereâs a python_script that will publish the payload 14163857 to the topic home/OpenMQTTGateway/commands/MQTTtoSRFB, pause for 1 second, then repeat this action for a total of 5 times.
fire_alarm.py
service_data = {'topic':'home/OpenMQTTGateway/commands/MQTTtoSRFB', 'payload':'14163857'}
for i in range(5):
hass.services.call('mqtt', 'publish', service_data, False)
time.sleep(1)