If-else in Rule

I have a rule that makes me crazy right now.
the ON/OFF part should only work if a LOCK is not ON and if its between 8 - 15h…so if i manually turn OFF the Solar_Switch it should not go on again if its for example 19h…but it does…

Any idea what i did wrong or is the reason for not working as described ?

rule "Solar_Automation"
when
    Item PVng5sum received update
then
    if ((Solar_Lock.state == OFF) && (now.getHourOfDay() >= 8) && (now.getHourOfDay() <= 15)) {
       if (PVng5sum.averageSince(now.minusMinutes(15)) > 150 && Solar_Switch.state == OFF) { sendCommand(Solar_Switch, ON) }
       else
       if (PVng5sum.averageSince(now.minusMinutes(15)) < 80 && Solar_Switch.state == ON) { sendCommand(Solar_Switch, OFF) }
    }
end

You are missing some braces: The else must also be separated off from the statements.

if conditions {
  statements
}
else {
  other statements
}

What you have is:

if conditions {
  statements
  else
  other statements
}

so the rule thinks the else is just another part of the statement series (and one that it ignores), not part of the if-control structure.

2 Likes

OK. so an elseif would also do the trick (instead of the else if)…i guess this would work the same way…if elseif exists?

Thanks!!!

Yes else if works the same way:

if conditions {
  statements
}
else if conditions {
  statements
}
else {
  other statements
}

hm, still struggling…

You mean that this part does something unintended…

       if (PVng5sum.averageSince(now.minusMinutes(15)) > 150 && Solar_Switch.state == OFF) { sendCommand(Solar_Switch, ON) }
       else
       if (PVng5sum.averageSince(now.minusMinutes(15)) < 80 && Solar_Switch.state == ON) { sendCommand(Solar_Switch, OFF) }

So this does not work?
if condition {statemens} else if condition {statements}

and you mean to do it that way:
if condition {statemens} else {if condition {statements}}

Do I get it right?

I apologize. From the formatting of your original post I misunderstood your intended logic. You are correct that

is the proper general construction for the if control structure. The only difference between what you have written here and what I posted above is whitespace.

I now understand what you are trying to achieve. What version of OH are you using?

sorry, did not mention … still 2.5.12, on the preparation to 3.x

When having difficulty with if statements (or any problem really):

  • add lots of logging
  • break up long lines that do a whole lot into single lines that do less; you can move them back to one line once you figure out what is wrong.

Another technique I like is to test whether the conditions are right to even run the rule and if not exit. That can greatly simplify the following lines of code.

So the if you only want to run the rule when the lock is OFF, test for that up front.

    if(Solar_Lock.state == ON) return;

Now you don’t have to worry about it for the rest of the rule. If the lock is ON, it exits immediately.

Next you only care if it’s between 08:00 and 15:00. If the time falls outside that range you don’t want the rule to run.

    if(Solar_Lock.state == ON) return;
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) return;

Now you don’t have to mess with the hour either. If you get past those two lines you now know you need to do something. While trying to figure out the problems, add some logging so you know why nothing happens in those cases where nothing happens.

    logInfo("solar", "Running solar automation rule")
    if(Solar_Lock.state == ON) {
        logInfo("solar", "Skipping, lock is ON.")
        return;
    }
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) {
        logInfo("solar", "Skipping, it's not between 08:00 and 15:00")
        return;
    }
    logInfo("About to calculate the new Solar_Switch state")

Note that in OH 3 when using UI rules, the above could/should be put into the “But only if…” part of the rule. No actual code would be required actually.

With the logging we now know why the rule just exits and when it doesn’t just exit.

Next you use the same value twice so store it into a variable and log out it’s value.

    logInfo("solar", "Running solar automation rule")
    if(Solar_Lock.state == ON) {
        logInfo("solar", "Skipping, lock is ON.")
        return;
    }
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) {
        logInfo("solar", "Skipping, it's not between 08:00 and 15:00")
        return;
    }
    logInfo("solar", "About to calculate the new Solar_Switch state")

    val avg = PVng5sum.averageSince(now.minusMinutes(15)
    logInfo("solar", "Fifteen minute average is " + avg)

Now you know the raw numbers that you are working with. Let’s further simplify and wait to test the current state of Solar_Switch’s state until later and just save the new state into a variable and log that out.

    logInfo("solar", "Running solar automation rule")
    if(Solar_Lock.state == ON) {
        logInfo("solar", "Skipping, lock is ON.")
        return;
    }
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) {
        logInfo("solar", "Skipping, it's not between 08:00 and 15:00")
        return;
    }
    logInfo("solar", "About to calculate the new Solar_Switch state")

    val avg = PVng5sum.averageSince(now.minusMinutes(15)
    logInfo("solar", "Fifteen minute average is " + avg)

    var newState = "STAY"
    if(avg > 150) {
        newState = "ON"
    }
    else if(avg < 80) {
        newState = "OFF"
    }
    logInfo("solar", "The calculated new state is " + newState)

Finally, if newState isn’t “STAY” and it’s different from the switch’s current state, send it as a command.

    logInfo("solar", "Running solar automation rule")
    if(Solar_Lock.state == ON) {
        logInfo("solar", "Skipping, lock is ON.")
        return;
    }
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) {
        logInfo("solar", "Skipping, it's not between 08:00 and 15:00")
        return;
    }
    logInfo("solar", "About to calculate the new Solar_Switch state")

    val avg = PVng5sum.averageSince(now.minusMinutes(15)
    logInfo("solar", "Fifteen minute average is " + avg)

    var newState = "STAY"
    if(avg > 150) {
        newState = "ON"
    }
    else if(avg < 80) {
        newState = "OFF"
    }
    else {
        logInfo("solar", "Average is inside the hysteresis range, doing nothing");
    }
    logInfo("solar", "The calculated new state is " + newState)

    if(newState != "STAY" && newState != Solar_Switch.state.toString){
        logInfo("solar", "Commanding the Solar_Switch to " + newState)
        Solar_Switch.sendCommand(newState)
    }

This should give you all the information you need to understand what the rule is doing and why. Once you understand that you should have an idea what the logical error is and what needs to be done to fix it. Once you figure it out you can change the logInfos to logDebug to suppress them, or just remove them. If you remove them the rule will reduce to:

    if(Solar_Lock.state == ON) return;
    if(now.getHourOfDay() < 8 || now.getHourOfDay() > 15) return;

    val avg = PVng5sum.averageSince(now.minusMinutes(15)
    var newState = "STAY"
    if(avg > 150) newState = "ON"
    else if(avg < 80) newState = "OFF"

    if(newState != "STAY" && newState != Solar_Switch.state.toString){
        Solar_Switch.sendCommand(newState)
    }

Pay close attention to how I used the curly brackets above. If you have difficulty switching between the two styles, always use curly brackets.

The reduced rule is longer but it should be much easier to understand because you don’t have to do as much mental gymnastics to understand a single line of code. And it opens a lot more opportunities for adding logging to understand the rule.

8 Likes

Jesus, @rlkoshak
I do not know how many times you already provided great feedback to questions (mine and others i read during my research in the community). but this is another level for me. Totally makes sense and looks great + most efficient. Not comparable to what I organized on myself.

Wow, really happy about your reply!

maybe one final “easy” question - as i hate to collect variables. Do these variables survice outside the rule, or as soon as the rule is done, its variables are gone as well. - i guess so

All code has something called “scope”. Depending on the language, variables and scope are handled a little differently, but in general anything defined inside a scope is destroyed outside of the scope.

In Rules DSL you start a new scope with:

Start of new scope End of new scope
when end
{ }
[ ]

So if you define a variable with val or var before any of your rules, those variables exist and persist outside the rules and will remain defined and keep their state until the .rules file is loaded again. If you define a variable with val or var after a when, that variable will only exist while the code until the end is reached. At that point they are destroyed. If you define a variable after a { that variable will only exist until the the next } is reached.

Don’t be afraid of variables. Like Items, it doesn’t really cost anything to define and they can greatly simplify your rules.

1 Like