feature-requests icon indicating copy to clipboard operation
feature-requests copied to clipboard

Add variables to Scripts

Open CallMeAreks opened this issue 6 years ago • 6 comments

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

CallMeAreks avatar May 22 '19 10:05 CallMeAreks

yes it would be nice to be able to pass args to scripts to save copying and pasting blocks of yaml

OptimusGREEN avatar Aug 31 '21 12:08 OptimusGREEN

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?

eddiewebb avatar Sep 22 '22 10:09 eddiewebb

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...

nagyrobi avatar Sep 27 '22 20:09 nagyrobi

https://github.com/esphome/esphome/pull/3538

ssieb avatar Sep 27 '22 20:09 ssieb

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.

deosrc avatar Sep 28 '22 00:09 deosrc

  • 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 :)

nagyrobi avatar Sep 28 '22 07:09 nagyrobi