Thread::sleep within a timer? (or is there a better way?)

So I’m trying to write a rev 2.0 of my garage closing rules. Rev 1.0 was pretty good; it tried to shut the door for X number of times, emailed me each time it attempted, and if it failed Y number of times it (intentionally) gave up.

So far so good.

But I’ve noticed that occasionally, the garage door sensor I have will not register a door closed event. So the door is actually down, but either the transmission gets eaten or the door doesn’t quite seat right or whatever, the door is down but reads “up” in openhab. So then my current rule fires off and tries to shut the already closed door, and obviously fails Y times and gives up. (But not after starting to send me panic SMSes after a few tries, which is… annoying.)

Side note: the garage door opener/closer I have has a non-zwave-accessible tilt sensor, so that if the door is down and you send it a close command, it ignores the command entirely. (It also locks out any further commands for 30 seconds after receiving one.)

The fix to this situation is to raise the door, wait a minute, then close it again. Then either the door re-seats or the transmission works or whatever, but every time this has happened I’ve cycled the door and it’s fixed everything, so I’d like OH to be able to do that…

…trouble is that requires a thread::sleep in the middle of a timer block. And it doesn’t seem as though that’s allowed to happen, at least not so far as the OH Designer is concerned. So in my mind that means I’m trying to do something using a bad method, so looking for suggestions.

Here’s the pseudocode of what I’m trying to do:

  • Door goes up. Start X minute timer.
    — timer [re-] entry point

  • X timer goes off.

  • Tell garage door to go up.

  • Wait 1 minute

  • Tell door to go down, re-start X minute timer

  • Separately, on a door down event kill the X minute timer.

Thanks in advance!

did you try to create another timer within the action statement of the first time?
This is working

This is a bit subtle of an issue.

Within a Rule the OH Rule’s DSL does a whole lot of things for you behind the scenes. One of these things it does is to catch any exceptions that may be thrown inside the rule.

Unfortunately, a Timer’s body is a lambda that is running outside of a rule. Consequently you do not have that safety net built in and if you use a method that says it throws an exception you must provide code to catch it. Thread::sleep is one such method. Thus to use Thread::sleep within a Timer you have to put it inside a try/catch block as follows:

try { Thread::sleep(60000) } catch(InterruptedException e) { }

I believe this also holds true for other types of lambdas (i.e. within a forEach and globally defined lambdas).

Ahhh, thanks Rich! Definitely wasn’t going to get there from here on my own.

I’ll give that a try and update on how I make out.

Hmmmm, not getting a result here… Did the try/catch routine, and the designer likes it, but it doesn’t seem as though it’s actually caught and executed. Here’s what I have:

        GARAGE_BIG_DOOR_TIMER = createTimer(now.plusMinutes(Garage_Big_Door_Timeout)) 
            [|
                logInfo("openhab","Garage big door door timer expired.  Sending email.")
                if (Garage_Big_Door_Reschedule_Multiplier == 1)
                    Mail_Subject = "Garage big door door was left open!"
                else
                    Mail_Subject = "Garage big door door is STILL OPEN!"
            
                var long Time_Difference_In_Minutes = (now.millis - Garage_Big_Door_Timer_Start_Time.millis) / 60000

                Mail_Message = String::format ("Garage big door left open for %1$s minutes!",Time_Difference_In_Minutes)                
                
                sendMail(Mail_Destination,Mail_Subject,Mail_Message + "  Raising, then attempting to shut.")

                logInfo("openhab","Raising garage big door (to re-seat)")
                postUpdate(GARAGE_BIG_DOOR_OPENER,OFF) // set status to OFF without sending physical command, so we can force send physical ON [open] command
                sendCommand(VIRTUAL_GARAGE_ACTIVATE_COURTESY_LIGHTS,ON)  // turn on the garage courtesy lights
                sendCommand(GARAGE_BIG_DOOR_OPENER,ON) // open the door!

                logInfo("openhab","Pausing garage big door rule for 45s")
                // unconditionally pause 45s without launching a second thread 
                try { Thread::sleep(45000) } catch(InterruptedException e) { 
                        logInfo("openhab","Resuming garage big door rule after pause.  Closing door (to re-seat).")
                        postUpdate(GARAGE_BIG_DOOR_OPENER,ON)  // set status to ON without sending physical command, so we can always send physical OFF [close] command!
                        sendCommand(GARAGE_BIG_DOOR_OPENER,OFF) // close the door!
                 }
                
                Garage_Big_Door_Reschedule_Multiplier = Garage_Big_Door_Reschedule_Multiplier + 1
 GARAGE_BIG_DOOR_TIMER.reschedule(now.plusMinutes(Garage_Big_Door_Timeout))

                if (Garage_Big_Door_Reschedule_Multiplier > 5)
                    {
                        logInfo("openhab","***GIVING UP ON BIG GARAGE DOOR OPENER!***  Attempted to shut door " + (Garage_Big_Door_Reschedule_Multiplier - 1) + "times!")
                        sendMail(Mail_Destination,"Giving up trying to shut big garage door!","Attempted to shut garage door " + (Garage_Big_Door_Reschedule_Multiplier -1) + " times.  Giving up!")
                        sendMail(Panic_Destination,"","Attempted to shut garage door " + (Garage_Big_Door_Reschedule_Multiplier -1) + " times.  Giving up!")
                        GARAGE_BIG_DOOR_TIMER.cancel
                        GARAGE_BIG_DOOR_TIMER = null                        
                    }
            ]

What I’d expect to see in the log is that it opens the door, pauses for 45s, resumes execution, then shuts the door. But that’s not what I’m seeing; I see it pause, then… nothing:

2016-05-05 07:10:04.345 [INFO ] [g.openhab.model.script.openhab] - Garage big door door opened.  Starting timer.
2016-05-05 07:10:04.386 [INFO ] [g.openhab.model.script.openhab] - Front courtesy light activated (courtesy light button), but it's daytime.  Doing nothing.
2016-05-05 07:10:04.466 [INFO ] [g.openhab.model.script.openhab] - Side courtesy light activated (courtesy light button), but it's daytime.  Doing nothing.
2016-05-05 07:10:06.360 [INFO ] [g.openhab.model.script.openhab] - Garage outside courtesy light activated ([garage] courtesy light button), but it's daytime.  Doing nothing.
2016-05-05 07:10:07.297 [INFO ] [g.openhab.model.script.openhab] - Turning on garage interior courtesy light.  ([all] courtesy light button)
2016-05-05 07:10:07.645 [INFO ] [g.openhab.model.script.openhab] - Garage lights turned on.  Starting timer.
2016-05-05 07:12:06.217 [INFO ] [g.openhab.model.script.openhab] - Garage big door door timer expired.  Sending email.
2016-05-05 07:12:08.193 [INFO ] [g.openhab.model.script.openhab] - Raising garage big door (to re-seat)
2016-05-05 07:12:08.654 [INFO ] [g.openhab.model.script.openhab] - Garage outside courtesy light activated ([garage] courtesy light button), but it's daytime.  Doing nothing.
2016-05-05 07:12:08.945 [INFO ] [g.openhab.model.script.openhab] - Turning on garage interior courtesy light.  ([garage] courtesy light button)
2016-05-05 07:12:09.006 [INFO ] [g.openhab.model.script.openhab] - Pausing garage big door rule for 45s
2016-05-05 07:12:09.236 [INFO ] [g.openhab.model.script.openhab] - Rescheduling garage interior courtesy lights by 5 minutes.

[after two minutes or so watching the garage cameras, I give up and manually shut the door myself.]

2016-05-05 07:14:42.633 [INFO ] [g.openhab.model.script.openhab] - Garage big door closed.  Killing timer.

So… any ideas? Near as I can figure thread::sleep is in milliseconds, so 45K should be ~45s, +/- 100ms. (Heck even if it was +/- 10s it’d still be fine for my use!)

OK, I think you are misunderstanding how try/catch works. I’m going to make this response somewhat generic and expansive so I can reference it later.

The tl;dr is the stuff in the catch clause only gets executed if the exception is thrown and exceptions are only thrown when there was a problem. So your “Resuming garage big door rule after pause” code never runs. Move that outside the catch’s { } and it should work.

Many programming languages have the concept of exception handling (C++, Java, C#, Python, etc.). Exception handling is an approach to dealing with errors where when an error case is encountered a little object called an exception is created with a message and some additional information about where the error occurred (that big long stack trace you see in the logs when something goes wrong is some of the information contained in an exception). This little exception object is then “thrown” which means that all execution halts and the program backs up through the stack of the program until it encounters a catch for that exception. When the catch is found the code inside the catch clause is executed. Presumably this code would be error handling code.

Exception handling usually takes the form of three clauses:

  1. try: the code inside the curly brackets can potentially generate an exception that needs to be caught
  2. catch: the code inside the curly brackets will execute if the code inside the try block threw that type of exception
  3. finally: whether or not an exception was thrown the code inside the finally clause is guaranteed to execute

Most of the time what you will see inside the catch clause is a logging statement that prints out the exception which generates those nice stack traces you see in the logs. Occasionally you will see a completely empty catch clause which essentially means you don’t care whether the error occurred and this is the case here.

When you call Thread::sleep it is possible for some other thread to “interrupt” this thread and force it to start executing again before the sleep finishes. In that case an InterruptedException will be thrown. I do not think there is a single case where this would ever occur within a rule (unless OH is shutting down in which case you still don’t care) so this exception can safely be ignored (i.e. have an empty catch block).

Now a little discussion about limitations of exceptions in the Rules DSL. In most languages with exception handling the stack trace inside the exception is incredibly useful because it tells you exactly which line generated the error. Unfortunately the nature of the Rules DSL works against the rules developer to make the meaningfulness of an exception much less useful. When an exception is thrown in a rule it cannot tell you which line in your rule generated the error, only that the error occurred somewhere inside your rule. Furthermore a whole lot of different types of errors get wrapped up into a NullPointerException.

For example, in Java if you try to parse “123ABC” into an Integer you will get a ParseException. If you call Integer::parse(“123ABC”) inside a rule you will also get a ParseException. But if you try to sendCommand(myNumberItem, “123ABC”) you will see a NullPointerException. :confounded:

This is why interpreting errors caused by rules can be such a chore and it is definitely one area where the JSR233 rules have an advantage.

Ahhh, thanks! For some reason I was under the impression that thread::sleep throws an exception at the end of the sleep cycle. Guess not! I’ll give that a go when I get a chance.

That got it all fixed up! Now my garage door will try to re-seat itself if it thinks it’s been left up. Thanks for the help!