WTH. Script execution stops after a condition step in script sequence, rather than continue past

In a script, if a “condition” step evaluates to “false”, then the script exits early rather than continue execution.

If it’s too complex to implement “if/elsif/else” logic, perhaps a new step called “Condition-Continue” or somesuch.

Example of when this matters:

I have a script that turns on my receiver, TV, and Playstation. But if house mode is set to “Quiet”, I want the receiver to mute itself immediately after it turns on to avoid waking people up.

But I can’t do that immediately, since that would require a conditional at the top of the script. If it’s not set to Quiet, then nothing else gets turned on.

In addition, I can’t then do multiple conditionals like “If Quiet, mute the receiver, and if nighttime, apply ‘Theater’ scene”

Have a look at the choose feature introduced in 0.113. Effectively it implements a chain of if-elif statements (case is the term used in other languages). If the first condition evaluate to false it proceeds to the next condition and so on and so on until a condition evaluates to true or ends at the optional default section.

The example below is from the documentation for script syntax. If the first condition isn’t met (you can have many more) it proceeds to execute the default: section.

automation:
  - trigger:
      - platform: state
        entity_id: binary_sensor.motion
    mode: queued
    action:
      - choose:
          # IF motion detected
          - conditions:
              - condition: template
                value_template: "{{ trigger.to_state.state == 'on' }}"
            sequence:
              - service: script.turn_on
                entity_id:
                  - script.slowly_turn_on_front_lights
                  - script.announce_someone_at_door
        # ELSE (i.e., motion stopped)
        default:
          - service: light.turn_off
            entity_id: light.front_lights

Yes, I use choose in some of my scripts, and it’s a fantastic addition to automations and scripts.

But in my “turn on playstation” example above, wouldn’t I then have to duplicate all the subsequent steps (turn on tv, turn on playstation, etc) in both the true block, and the default block?

Also, am I able to implement my second example of multiple, unrelated conditionals? That is:

  1. Turn On receiver
  2. If Quiet mode, set volume to 0
  3. Turn on TV
  4. Turn on Playstation
  5. If nighttime, apply scene “Theater”

The default part is optional. If you leave it out then you have an if without an else.

1 Like

You can break that duplicate code into a script.

Right. And that’s the kind of thing I’ve had to do. My “WTH” suggestion is to make that possible without having to use workarounds like adding more scripts.

Which it sounds like I can do using choose without a default as per @pnbruckner’s suggestion (though… that’s still in the “workaround” category for me, since it’s not an obvious way to use switch/case/choose statements).

Ah! That makes sense. Thanks for pointing that out – I’ll definitely start using that trick.

I still consider it something of a “WTH”, though. A programmer wouldn’t normally think to use “choose” in that way, so it would be nice to have a construct that’s more intentional and clear for that purpose.

Neither is switch/case to a someone who’s never used it before. Scripts are not a “workaround” any more than a subroutine or function is a “workaround” in other languages.

Anyway, if you believe you know of an easier way it should be done, please present an example of the syntax you would prefer to see for, say, a choice of three possible options. You can modify the following example to show how it could be simplified with different syntax:


- alias: example
  trigger:
    platform: state
    entity_id: sensor.whatever
  mode: queued
  action:
    - choose:
        - conditions:
            - condition: template
              value_template: "{{ trigger.to_state.state|int == 100 }}"
          sequence:
            - service: script.this
        - conditions:
            - condition: template
              value_template: "{{ trigger.to_state.state|int < 50 }}"
          sequence:
            - service: script.that
        - conditions:
            - condition: template
              value_template: "{{ trigger.to_state.state|int > 150 }}"
          sequence:
            - service: script.the_other
      default:
        service: notify.notify
        data_template:
          title: Uh-oh
          message: "Problem! Value is {{trigger.to_state.state}}"

FWIW, I believe pnbruckner is also working on a simplification of how conditions are presented when using the template platform. I’ve seen an example and it’s more streamlined than how it’s currently done.

BTW, I did use the adjective “optional” when describing default. Maybe you missed it.

You can use multiple choose in your script sequence or automation action
So

- service: #whatever you use to turn receiver on
- choose:
    - conditions:
          #Your condition to check quiet mode
      sequence:
         # your actions to set volume
- service: # service to turn TV on
- service: # service to turn playstation on
- choose:
  - conditions:
      #check if it's night- time
     sequence: 
      # apply your theme

Leave the default out if you don’t need it.

1 Like

The choose action was a compromise. It was discussed at length in an architecture issue. A more straightforward if - elif - else construct would have been preferable, but YAML constraints made it difficult. So, yeah, I agree, choose is neither optimal nor intuitive, but it was the best that could be suggested at the time.

2 Likes

Maybe it is because I have a little programming experience, but I think it would still be clearer (easier to understand) to just call it ‘if/else if’ instead of 'choose" and ‘else’ instead of ‘default’.

Nothing inherently wrong with the term choose. Other languages use different names, for example, this is from VB:

Select [ Case ] testexpression  
    [ Case expressionlist  
        [ statements ] ]  
    [ Case Else  
        [ elsestatements ] ]  
End Select

It uses the word ‘select’ instead of choose, each selection begins with the word ‘case’ and the equivalent of ‘default’ is ‘case else’.

In C++, switch is the equivalent word for choose, each choice begins with case and it uses the word default in the same way VB uses case else.

In perl 5, it’s given, when, and default, respectively. Prior to version 5 it was switch case.

When learning python, it was surprising to discover there’s no equivalent statement for switch/choose/select/given (or, for that matter, do while). Nevertheless, an old-school chain of if-elif's can achieve the same thing (it just looks kind of lame when you’re accustomed to using switch or select).

I’m beginning to believe that the choose construct was a mistake. :laughing: :cry: It seems to confuse everyone.

I think this would be better (and now that I’ve had a chance to think about it more, it wouldn’t really be any more difficult to implement than choose was):

- if:
  <conditions>
  then:
  <actions>
- elif:
  <conditions>
  then:
  <actions>
- else:
  <actions>

where <conditions> are one or more conditions:

- if:
  - condition: state
    entity_id: binary_sensor.abc
    state: 'on'
  - condition: template
    value_template: "{{ abc == '123' }}"

and <actions> are one or more actions:

  then:
  - service: script.abc
  - event: xyz

and elif and else are both optional, meaning you could use one, or the other, or both, or neither.

1 Like

That’s similar to how ESPHome handles if statements in YAML. Except it omits an elseif.

For example.

- if:
  condition:
    api.connected:
  then:
    - logger.log: "Connected to the Home Assistant API!"
  else:
    - logger.log: "NOT connected to Home Assistant API!"

That was another option, but then it would quickly lead to ridiculous indentation with nested if’s. I prefer something with elif to keep things reasonable.

1 Like

Unless I’m mistaken, I believe choose was suggested in the Architecture discussion and inspired by XSL. However, what was lost in translation is that XSL’s choose includes other key words to clarify its usage:

<xsl:choose>
  <xsl:when test="expression">
    ... some output ...
  </xsl:when>
  <xsl:otherwise>
    ... some output ....
  </xsl:otherwise>
</xsl:choose>

The key word otherwise became default and when effectively disappeared and simply inferred by condition.

I don’t have a problem with understanding the new choose option’s structure. Frankly, I dislike the if-then-elif-then-else example (shown above) because it contains more key words to achieve what can already be done with less.

I think choose will become even easier to use when your PR to simplify the (template) conditions statement is implemented.

From the example in your PR:

- choose:
    - conditions: "{{ trigger.to_state.state == 'home' }}"
      sequence:
        - ...
    - conditions: "{{ trigger.to_state.state == 'office' }}"
      sequence:
        - ...

Which one? There are two. Because the one I suggested is actually slightly more compact than the choose equivalent.

It doesn’t seem like it will. And even if it does, the same would apply to if & elif actions.

While all the suggestions are valid here and I appreciate @pnbruckner’s continued contributions to make this easier still, I feel like the author’s original point is still valid - why doesn’t execution continue after a conditional? I feel like whether or not to continue execution after should be a decision users can make regardless of the conditional construct or whether or not a default is present.

To give an example, when someone arrives home at my house I conditionally turn lights on downstairs based on current light levels in those rooms. Then I always turn the outlets on regardless of which lights turned on since those are for two devices with screens, light level doesn’t matter to them. Today I would need to do the following to make that work:

  1. Remember to do the outlets first since those are always and once conditionals start the script ends
  2. Move all but one of the conditionals into a separate script since logic cannot proceed within this script past a conditional
  3. Move that one remaining conditional to the end (or just put them all in separate scripts because this is kind of a hassle)

I don’t mind that its possible for script execution to end after a conditional but I do feel like that should be an option rather then a requirement.

The first one here. if-then-elif-then-else Currently, then is not needed.

There are two different action types being discussed: condition and choose. The former’s whole purpose in life is to provide a conditional “exit” type statement. The latter allows a “do something only if something else is true” type statement. So basically, don’t use the first when the second is more appropriate. I believe that was the suggestion in the first reply.

The discussion, though, has lately turned into “WTH is an exit statement called condition, and why is an if statement called choose”.