Add variables to Scripts
Describe the problem you have/What new integration you would like
To be able to declare variables in scripts. Currently the docs say the script feature is meant for code reuse. And has this example:
script:
- id: my_script
then:
- switch.turn_on: my_switch
- delay: 1s
- switch.turn_off: my_switch
But that is only useful if you're toggling the same switch in multiple places of your config file. Not useful at all if you want to reuse logic toggling different switches or outputs. Please describe your use case for this integration and alternatives you've tried:
Currently I have something like this:
turn_on_action:
- lambda: 'id(last_command) = 3;'
- output.turn_on: disarm
- delay: 1000ms
- output.turn_off: disarm
- component.update: alarm_text_status
For every action my alarm system allows and it seems to be very little that can be done to avoid repeating similar blocks.
Additional context
yes it would be nice to be able to pass args to scripts to save copying and pasting blocks of yaml
User Defined APIs can declare variables. I need to pass those to a script (which is long running) so I can also then call script.stop from another service.
AFAIK there is no way to pass those API values to a script?
I have a user case which I think cannot be solved without having the possibility to pass arguments to script. See below:
The problem I have a Sonoff RF Bridge with 14 configured Time Based covers.
This is how a cover config looks like:
cover:
- platform: time_based
name: 'My Room Cover'
device_class: shutter
assumed_state: true
has_built_in_endstop: true
close_action:
- rf_bridge.send_raw:
raw: 'AAB0XXXXX....XXXXXXXXXX'
close_duration: 34
stop_action:
- rf_bridge.send_raw:
raw: 'AAB0YXXXX....XXXXXXXXXX'
open_action:
- rf_bridge.send_raw:
raw: 'AAB0ZXXXX....XXXXXXXXXX'
open_duration: 36s
And there are 14 such covers.
When you operate the covers one by one, by hand, everything is fine. Each cover opens or closes as it should.
The problem occurs when you want to use automations in HA to operate multiple covers at once:
service: cover.close_cover
target:
entity_id:
- cover.my_room_1
- cover.my_room_2
- cover.my_room_3
In this case only the first one in the list, cover.my_room_1 reacts, the others don't move. I look at ESPHome debug log on the node, and everything seems to work as expected from the node's point of view:
[21:40:53][D][rf_bridge:213]: Sending Raw Code: AAB0ZXXXX....XXXXXXXXXX <- close code for cover.my_room_1
[21:40:53][D][rf_bridge:213]: Sending Raw Code: AAB0ZXXXX....XXXXXXXXXX <- close code for cover.my_room_2
[21:40:53][D][rf_bridge:213]: Sending Raw Code: AAB0ZXXXX....XXXXXXXXXX <- close code for cover.my_room_3
What I found by trial and error is that the cover motors simply don't accept the RF codes transmitted by the RF Bridge if they are sent too fast after each other. The log above shows that they are alI sent very quicly one after another in the same second. I am able to reproduce this with Tasmota too. From my experience it needs at least 500-600ms of silence between transmissions. If I change the automation in HA to:
- service: cover.close_cover
entity_id: cover.my_room_1
- delay: 00:00:00.750
- service: cover.close_cover
entity_id: cover.my_room_2
- delay: 00:00:00.750
- service: cover.close_cover
entity_id: cover.my_room_3
The covers will all close with a little delay in between. I used 750ms to be on the safe side.
This workaround makes the HA automations very cumbersome.
The requested solution
In ESPHome we could make an RF code sender script, which has queued mode, and would accept a variable parameter, like this:
script:
- id: rf_transmitter_queue
mode: queued
then:
- rf_bridge.send_raw:
raw: !variable
- delay: 750ms
And we could have the covers configured to run the script instead:
cover:
- platform: time_based
name: 'My Room Cover'
device_class: shutter
assumed_state: true
has_built_in_endstop: true
close_action:
- script.execute: rf_transmitter_queue
variable: 'AAB0XXXXX....XXXXXXXXXX'
close_duration: 34
stop_action:
- script.execute: rf_transmitter_queue
variable: 'AAB0YXXXX....XXXXXXXXXX'
open_action:
- script.execute: rf_transmitter_queue
variable: 'AAB0ZXXXX....XXXXXXXXXX'
open_duration: 36s
How would it work? Very nice, because the queued script would wait 750ms between each next run, which could accept a variable parameter containing the RF code to be sent.
This way, HA wouldn't have to know about any manual delays and automation editing would be smooth and nice.
But I'm open to any other suggestions...
https://github.com/esphome/esphome/pull/3538
I have a user case which I think cannot be solved without having the possibility to pass arguments to script. See below:
...
But I'm open to any other suggestions...
Esphome scripts supporting parameters would definitely be easier but here's a few alternatives:
- Add the delay into each action. Bit annoying but with 14 covers and 3 actions each, it shouldn't take too long and you could use a substitution to make it easy to adjust the length of all of the delays at once. This is what I've done in a few places copy pasting actions into multiple places as it keeps it easy to use in HA automations and dashboards.
- Create a home assistant script and use that in the automations instead. Bit less friendly for writing the auotmations but prevents the duplication of the delay everywhere. Just remember to use the right call to start the script so that it will wait until it finishes.
- Add the delay into each action. Bit annoying but with 14 covers and 3 actions each, it shouldn't take too long and you could use a substitution to make it easy to adjust the length of all of the delays at once. This is what I've done in a few places copy pasting actions into multiple places as it keeps it easy to use in HA automations and dashboards.
That's completely useless because it doesn't build up a queue. With a fixed same delay before each transmission, the result will be the same: commands will be sent out at once, but after that delay (they will be waiting in parallel, not in series...)
- Create a home assistant script and use that in the automations instead. Bit less friendly for writing the auotmations but prevents the duplication of the delay everywhere. Just remember to use the right call to start the script so that it will wait until it finishes.
That's what I already have, and want to get rid of.
With help from @ssieb through ESPHome's Discord channel, the alternative solution to send commands to multiple covers at once from HA (until the PR above will be merged) is:
globals:
- id: rf_code_queue
type: 'std::vector<std::string>'
number:
- platform: template
name: Delay commands
icon: mdi:clock-fast
entity_category: config
optimistic: true
restore_value: true
initial_value: 750
unit_of_measurement: "ms"
id: queue_delay
min_value: 10
max_value: 1000
step: 50
mode: box
script:
- id: rf_transmitter_queue
mode: single
then:
while:
condition:
lambda: 'return !id(rf_code_queue).empty();'
then:
- rf_bridge.send_raw:
raw: !lambda |-
std::string rf_code = id(rf_code_queue).front();
id(rf_code_queue).erase(id(rf_code_queue).begin());
return rf_code;
- delay: !lambda 'return id(queue_delay).state;'
cover:
- platform: time_based
name: 'My Room 1'
disabled_by_default: false
device_class: shutter
assumed_state: true
has_built_in_endstop: true
close_action:
- lambda: id(rf_code_queue).push_back("AAB0XXXXX....XXXXXXXXXX");
- script.execute: rf_transmitter_queue
close_duration: 26s
stop_action:
- lambda: id(rf_code_queue).push_back("AAB0YXXXX....XXXXXXXXXX");
- script.execute: rf_transmitter_queue
open_action:
- lambda: id(rf_code_queue).push_back("AAB0ZXXXX....XXXXXXXXXX");
- script.execute: rf_transmitter_queue
open_duration: 27s
The delay in the queue can be configured from UI at runtime :)