There's no "elseif" in Rules API, but depth limit is causing server overloads

I was porting a WebCoRE automation to Rules API

It was originally like this.

if button gets pressed {
  if (A) {
     do_1
  } else if (B) {
     do_2
  } else if (C) {
     do_3
  } else if (D) {
     do_4
}

But problem is, since there’s no “elseif” in Rules API, so it should be like this in Rules API.

if button gets pressed {
  if (A) {
     do_1
  } else{
     if (B) {
        do_2
     } else {
        if (C) {
          do_3
       } else {
         if (D) {
          do_4
         }
      }
    }
  }
}

But the problem is…
If I port this psedocode into Rules API, I get following error message

"message":"Nested action depth limit (5) exceeded"

Duh…

So I did the workaround like below for the depth limit.

if button gets pressed {
  if (A) {
     do_1
  }
  if ((not A) and B) {
     do_2
  } 
  if ((not A) and (not B) and C) {
     do_3
  } 
  if ((not A) and (not B) and (not C) and D) {
     do_4
}

Now it works well though…
But it takes MUCH MORE server resource than the original logic.

Let’s say when condition A is met, original code just exits after do_1
but, after the workaround, it needs to go over every useless following if statements, which contain a lot more comparison logics than the original one.

I know you guys need depth limits to alleviate server overloads,
but in the real world situation, not only it makes hassle in coding in rules API, but the depth limit is actually causing much more overload to server resources.

I suggest one of the solutuion to this problem.

  • Implementing “elseif” in the Rules API
  • or increasing the depth limit.

Thank you.

1 Like

Couldn’t you just use an additional local bool variable, init that to false and use that to determine whether you already have evaluated a previous condition to true? Something along the lines of

if button gets pressed {
  processed = false
  if (A) {
    do_1
    processed = true
  }
  if ((not processed) and B) {
    do_2
    processed = true
  }
  if ((not processed) and C) {
    do_3
    processed = true
  }
  if ((not processed) and D) {
    do_4
    processed = true
  }
1 Like

There’s no variable in rules api.

Hi! Not all conditions must be nested to be used, you can apply the trigger property to set them as a precondition so they don’t trigger independently.
For example, the Rule below follows this workflow:

  1. IF the Dimmer 1 is turned ON
  2. IF the Dimmer 2 is “OFF”, then the Dimmer 3 is turned ON
  3. IF the Dimmer 3 has a level of 75%, then the Dimmer 2 is turned ON

Points two and three are executed only when the condition of one is TRUE.

1 Like

First,
with this method, I can reduce 1 depth with my code with the first condition, like below

if button gets pressed :
if (A) {
  do_1
}
if ((not A) and B) {
   do_2
} 

However, there’s still problem persists with the provided rules code, because of absence of elseif.

Let’s say “Dimmer Bulb 3” was turned off with it’s brightness 75% just before turning off, and "Dimmer 2 is already turned OFF.

If I turn on the Dimmer 1 to trigger the automation
then following happens

  1. IF the Dimmer 1 is turned ON
    → It triggers the automation

  2. IF the Dimmer 2 is “OFF”, then the Dimmer 3 is turned ON
    → since the dimmer 2 is off, Dimmer 3 is turned ON.
    and because Dimmer 3 was 75% just before turning off, it is turned on with brighness 75%

  3. IF the Dimmer 3 has a level of 75%, then the Dimmer 2 is turned ON
    → this condition is also met because Dimmer 3 just got turned on with 75%, dimmer 2 is getting turned on, which is not an expected behavior, and waste of useless computation.

    If it were “else if” or “else+if (with one more depth)”, instead of just “if”, Dimmer 2 would not turned on as expected.

I already had this problem with my rules with the workaround. I had to use else + if.

Problem is the depth limit.

@nayelyz A few questions pertaining to your post:

From your example I gather that the top level (if)action conditions form an implicit and? I would have expected that the and sub section of the if action would be be used for this otherwise.

Thanks for more information on use of the trigger value. Is it sole use to create pre-conditions by specifying “Never” as the value? I note that the default value is “Auto” - what is the logic for that?

In general, is there more documentation to be found for the rules in general? I found an earlier post about a beta program for the rules API that supposedly would include some more docs and have submitted an application, but without receiving an answer so far.

Sorry for the delay, I was verifying some details about this with the engineering team.

trigger is a feature that allows users to customize the behavior of the events subscriptions for the Rule.
Auto is designed to best match the user’s intention but this is not always achieved so that’s why the value Always is available, to force the subscription to certain events in order to trigger the Rule.

I’m not sure if I understood correctly your question, but it’s something similar to:

if(A){
    if(B){...}
    if(C){...}
}

If it had the and operand, the conditions defined inside would have to be True to execute the Rule (otherwise, it’ll just run the else part if you included it). In my sample, only the first condition triggers the Rule but both conditions are evaluated as well.

I found “sequence” in rules API documentation.
If I write the code using “sequence” with “Parallel” like below, would it solve the problem that I mentioned above?

The problem was the result of the command by the second “if” affecting condition of 3rd “if” statement.

name: Rule with several conditions using sequence
actions:
- if:
    sequence:
      then: Parallel
    equals:
      right:
        device:
          devices:
          - Dimmer1-ID
          component: main
          capability: switch
          attribute: switch
      left:
        string: 'on'
    then:
      - if:
          equals:
            right:
              device:
                devices:
                - Dimmer2-ID
                component: main
                capability: switch
                attribute: switch
                trigger: Never
            left:
              string: 'off'
          then:
          - command:
              devices:
              - Dimmer3-ID
              commands:
              - component: main
                capability: switch
                command: 'on'
      - if:
          equals:
            right:
              device:
                devices:
                - Dimmer3-ID
                component: main
                capability: switchLevel
                attribute: level
                trigger: Never
            left:
              integer: 75
          then:
          - command:
              devices:
              - Dimmer2-ID
              commands:
              - component: main
                capability: switch
                command: 'on'

1 Like

Yes, elseif would be nice.
I ran into this …

… right away.
I don’t understand how they count but I can’t “exceed” 4 “if” expressions (and, I would claim, by definition, the top one should not count as it is not “nested” under another).
What resource is being protected by such a limit?

i just ran into an interesting issue due to not having the else if. i have a rule that if a light switch is double tapped down, then set the level at 1, if double tapped up, then set the level to 100. i have this for 8 rooms, and to avoid 16 rules, i added them in one as @nayelyz had suggested

actions
if (switch A double down) then…
if (switch A double up) then…
if (switch B double down) then…
if (switch B double up) then…

what actually happened was, if i hit switch A double down, then ALL THE OTHER LIGHTS TURNED on… certainly not what i would expect. now i need to break this into separate rules?

{
	"name": "Switch DBL Click (Dim)",
	"actions": [
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch A UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch A UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch A UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch A UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch B UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch B UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch B UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch B UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch C UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch C UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch C UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch C UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch D UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch D UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch D UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch D UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch E UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch E UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch E UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch E UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch F UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch F UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch F UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch F UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch G UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch G UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch G UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch G UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch H UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "down_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch H UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 1
										}
									]
								}
							]
						}
					}
				]
			}
		},
		{
			"if": {
				"equals": {
					"left": {
						"device": {
							"devices": [
								"{switch H UID}"
							],
							"component": "main",
							"capability": "button",
							"attribute": "button"
						}
					},
					"right": {
						"string": "up_2x"
					}
				},
				"then": [
					{
						"command": {
							"devices": [
								"{switch H UID}"
							],
							"commands": [
								{
									"component": "main",
									"capability": "switchLevel",
									"command": "setLevel",
									"arguments": [
										{
											"integer": 100
										}
									]
								}
							]
						}
					}
				]
			}
		}
	]
}

I would probably go with a rule for each room.

actions
if (switch A double down) then do this, else if (switch A double up) do that

You are allowed to nest if.

@mrfitz98 , Thanks for the tip, i agree that would work and was aware that we can nest, just limited depth and it is what i will end up doing. There are many ways to go about it, an ElseIf would be great to have, but i understand the complexities that it would cause under the hood and, its not catastrophic, there was ways around it for sure.

The real point I was trying to make (i guess) was the unexpected behavior that a trigger from one switch was somehow falling through and turning on all the lights, as if each separate trigger happened simultaneously, but yet not quite…

If Switch A was double tapped down, then it’s light would be set at 1, as expected, but all other lights mentioned in the file would turn on at their previous level, so its not actually executing all THEN blocks, its just affecting all the devices that appear in the same file which is wrong behavior and should be looked into.

I find that json hard to read without indenting. The </> in the editor allows you to paste and maintain the formatting.

{
  "name": "Push Noti",
  "actions": [
    {
      "notification": {
        "push": {
          "title": "Hello",
          "message": "Hello"
        }
      }
     }
  ]
}

I recall reading in one of these threads that it will only permit nesting to 3 levels, so I’ve been holding to that limit.

Yes, that is what I would expect. Buttons don’t have a standby state. If you double down on switch A the button attribute will be set to down_2x. It will stay down_2x until you do something other than double down on it.

So if you then double down on B the first condition is still true, the button of A is still down_2x.

It will always work through all 16 conditions and by the sounds of it will often find eight matches as each button retains its last attribute.

Neither nesting or ‘elseif’ is going to help much as the Rules API doesn’t currently have the equivalent of webCoRE’s gets or changes which explicitly check the event that just happened instead of the current state.

You basically need a separate rule (in the file sense) for each button.

Update: I originally wrote pushed when I meant gets.

1 Like

Interesting that you chose a push notification for your example. I’ve been trying to get them to work for a very long time now and I always get an error reported on ‘notification’.

Yeah, notifications are an unsupported action, but they did provide examples. It was the shortest example I could think of. :wink:

I don’t know if it would be the same but there’s a property “changes” in the Rules API, we used it for this example to mirror the behavior between two devices:

Also, the sample included in the documentation combined with “lessThan” (here) mentioned that it will be evaluated as true only when the number drops from a higher one and meets the condition of “lessThan” but not if the new value is still “lessThan” the number defined. So, it can help in your case, @Steve_AZ.

1 Like

sorry about that, it looked fine until i hit the post, then it all crapped out… i’ll fix the example

@orangebucket;the explanation makes sense, and explains what i am observing. I’ll have to test out the ‘changes’ property that @nayelyz mentioned, see if that makes it better.

Yes, I remember it well. The changes condition is defined as being true when its evaluation changes from false to true.

With a button the value only changes when you do something different with it so there is no change to detect if down_2x is followed by another down_2x.

I also later remarked that I’d never seen operand used before, as it was when mirroring switchLevel, but that I didn’t understand how it could be used with changes because the operand evaluated to the switchLevel which isn’t a boolean, so how can it have changed from false to true? That was never resolved.

In webCoRE, the changes trigger condition is a different animal. An example would be SwitchA switch changes to on. In order for that to be true the event that caused the piston to run had to be from the SwitchA switch attribute, it had to be on and the last time the condition was evaluated for a SwitchA switch event it was NOT on, so there was a change.

WebCoRE also has a gets trigger condition (I mistakenly called it pushed before) that is used in the form ButtonA button gets pushed that is true if the event that caused the piston to run was from ButtonA button and the value was pushed. So that explicitly detected the button event.