Rule Logic Check

Could someone please take a look at this rule and let me know if it has any problems or if there is a better way to write it. I temporarily changed the time period and tried it out earlier and it worked. All I want to do is close the overhead garage door between 12 midnight to 6am if it was left open, whether I had forgot to close it earlier in the day or my son came home and forgot to close it. Thanks for any help.

 import org.openhab.model.script.actions.Timer
 var Timer overheadTimer

 rule "Overhead Garage Door Auto-Close"
 when
         Item OverheadGarageDoor changed from CLOSED to OPEN
 then
    var Number hour = now.getHourOfDay
        if ((hour >= 24) || (hour <= 6)) { 
        if(OverheadGarageDoor.state==OPEN) {
        logInfo("Security", "Overhead Garage Door Auto-Close Timer Activated")
        overheadTimer = createTimer(now.plusMinutes(5)) [|
        sendCommand(OverheadGarageDoorSwitch, ON)
        logInfo("Security", "Overhead Garage Door Auto-Close Activated")
        sendMail("xxxx@xxxx.net", "Security", "Overhead Garage Door Auto-Close Activated") 
        ]
        }
        } else {
        if(overheadTimer!=null) {
           overheadTimer.cancel
           overheadTimer = null
           } }     
end

OK, what you have written won’t work I don’t think. The problem is the rule will only trigger when the Garage door’s state changes. But you opened the garage door before midnight and after 6 am and it won’t trigger again until its state changes. In other words it won’t catch the case where you forgot to close it.

There are a host of ways to solve this but probably the simplest and closest to what you are trying to do would be the following:

rule "Overhead Garage Door Auto-Close"
when
    Time cron "* 0/15 * * ? *" // every 15 minutes
then
    if(now.getHourOfDay<6) { // NOTE I got rid of >=24 because midnight is actually 0, I changed the <=6 because that would cause the door to auto close anytime before 6:59 instead of before 6:00 which is what I interpret you actually want
        if(OverheadGarageDoor.state == OPEN) {
            logInfo("Security", "Overhead Garage Door Auto-Close Activated")
            // I don't see the need for a timer, whether you do it now or in five minutes is immaterial
            sendCommand(OverheadGarageDoorSwitch, ON)
            sendMail(xxxx@xxxx.net", "Security", "Overhead Garage Door Auto-Close Activated")
        }
    }
end

This rule triggers every 15 minutes. If you are between the hours of midnight and 6 (exclusive) and the door is open, it will automatically close it. Based on your code above I don’t see the point of the timer. However, I can see a case where the timer could be useful.

For safety and/or to handle the unusual but not unheard of case that you have to open the door at night maybe it would be nice to get that email warning a little before the door actually closes so you are not surprised by the door trying to close on you when you don’t expect it. That would look like the following:

rule "Overhead Garage Door Auto-Close"
when
    Time cron "* 0/15 * * ? *" // every 15 minutes
then
    if(now.getHourOfDay<6) { 
        if(OverheadGarageDoor.state == OPEN) {
            logInfo("Security", "Overhead Garage Door Auto-Close Activated")
            sendMail("xxxx@xxxx.net", "Security", "Overhead Garage Door Auto-Close Activated, closing in five minutes")
            Thread::sleep(300000) // five minutes
            sendCommand(OverheadGarageDoorSwitch, ON)
        }
    }
end

NOTE: with the above the cron polling period has to be longer than five minutes or else you risk the chance that two copies of the rule will execute at the same time because of the sleep and the ON command sent twice, presumably reopening the door. You could go back to a timer if you want to wait longer than the polling period of the rule between the email and closing the door.

1 Like

I’ll probably go with your second method. The 5 minute delay is if one of my sons comes home late. They would open the garage with there remote and sometimes park in the street. The five minute delay would give them time to get in before the garage closes if they happen to come home right at the polling period. Is there no way to check the current state of an item in the “when” line so that I wouldn’t have to do polling.?

No. Rules are event driven, not state driven. Similarly, there is no way to do an “and” in the when clause because two events will never happen at the exact same time.

I’m not a fan of polling either but for cases like these polling is the simplest way I’ve been able to figure out that covers all the edge cases.

As I said though, there is more than one way to implement this behavior. For example, here is an approach that is significantly more complex but uses timers instead of polling. Well, sort of. It actually implements a type of polling if the door opens before midnight and is left open but does it by rescheduling the timer instead of a cron job.

There are some advantages to this approach (e.g. the door closes five minutes after opening or very soon after midnight instead of needing to wait 15 minutes (or whatever you set your polling period to be). However it does so at a very significant increase in rule complexity.

 import org.openhab.model.script.actions.Timer
 var Timer overheadTimer = null

rule "Overhead Garage Door Auto-Close Door Opened"
when
    Item OverheadGarageDoor changed from CLOSED to OPEN
then
    if(overheadTimer != null && !overheadTimer.hasTerminated){ // a timer has already been set
        overheadTimer.reschedule(now.plusMinutes(5)) // reset to timer for five minutes from now
    }
    else {
        overheadTimer = createTimer(now.plusMinutes(5), [|
            if(now.getHourOfDay<6) { // Its night, close the garage
                logInfo("Security", "Overhead Garage Door Auto-Close Activated")
                // I don't see the need for a timer, whether you do it now or in five minutes is immaterial
                sendCommand(OverheadGarageDoorSwitch, ON)
                sendMail("xxxx@xxxx.net", "Security", "Overhead Garage Door Auto-Close Activated")
                overheadTimer = null
            }
            else { // It's day, check back in five minutes
                overheadTimer.reschedule(now.plusMinutes(5))
            }
        ]
    }
end

rule "Overhead Garage Door Auto-Close Door CLOSED"
when
    Item OverheadGarageDoor changed from OPEN to CLOSED
then
    // cancel the timer
    if(overheadTimer != null) {
        overheadTimer.cancel
        overheadTimer = null
    }
end

Another approach that eliminates polling entirely would be as follows:

 import org.openhab.model.script.actions.Timer
 var Timer overheadTimer = null

rule "Overhead Garage Door Auto-Close Door Opened"
when
    Item OverheadGarageDoor changed from CLOSED to OPEN
then
    var timerTime = now.plusDays(1).withTimeAtStartOfDay // midnight
    if(now.getHourOfDay<6 || now.getHourOfDay == 11 && now.getMinuteOfHour >= 55) { // its night time or close to it
        timerTime = now.plusMinutes(5) // in five minutes
    }

    if(overheadTimer == null || overheadTimer.hasTerminated) {

        // Schedule a timer to close the door if it is still open
        overheadTimer = createTimer(timerTime, [ |
            if(OverheadGarageDoor.state == OPEN){
                logInfo("Security", "Overhead Garage Door Auto-Close Activated")
                sendCommand(OverheadGarageDoorSwitch, ON)
                sendMail("xxxx@xxxx.net", "Security", "Overhead Garage Door Auto-Close Activated")
            }
        ]
    }
    else { // timer is already set, reschedule it
        overheadTimer.reschedule(timerTime)
    }
end

This approach though is a bit subtle and if I got something wrong will be a bit of a pain to debug. But once it works (or if I managed to get it right off the bat) it doesn’t poll at all. It simply sets a timer for midnight if you open the door between 6am and 11:55pm or it sets a timer for five minutes from now if it is between 11:55 and 6am. In either case, the door is closed if it is still open.

But as you can see we went from 13 lines of code for my polling rule with the five minute delay to 36 lines of code for my first timer solution and 27 lines of code for my second one. It is up to you to determine where limiting or eliminating the coding is worth all this extra code and complexity. In the forum, unless I’m trying to demonstrate a design pattern or help someone who specifically asks for it, I try to keep my examples as simple as possible.

I hope one of the approaches above works for you.

Edit: OOPS! There is a bug in my completely non-polling example above. I fixed it.

I think I’ll use the polling method. I definitely want to keep it simple. Thank you.