oH 3.1 Looping timer until Thing is ONLINE

Hi there,

i had some spare time and finally wired two Shellys into my home.
One is working in my office as an edge switch together with the normal wall switch.
This was very important for the WAF!
So i´m able to control the lights of the office with the Shelly and with the wall switch.
Now i´m using my MS Teams Status Updater to turn on the Shelly in the morning and then control the lights.

As the hue lights are OFFLINE and can´t be controlled right after turning them ON with the Shelly, i need to wait until the Things come ONLINE.
This time can be something between 2 and 20 seconds for what i saw in the logs.

Now i had the idea to do a Looping timer as of Richs example.

I´m not sure what i´m missing but the timer gets rescheduled even after the Thing came ONLINE.

val String ruleId = "TestRule"
var Timer timer = null

rule "Test"

when

    Item itmTestSwitch changed

then

    timer = null

    logInfo(ruleId, "Test rule started!")

    var thingStatus = getThingStatusInfo('<Thing-UID>').getStatus().toString()
    logInfo(ruleId, "Thing is " + test)

    if(thingStatus == "ONLINE")
    {
        logInfo(ruleId, "Status Online!")
    }

    timer = createTimer(now, [ |
        if(thingStatus == "ONLINE")
        {
            timer = null
            logInfo(ruleId, "Test rule executed!")
        }
        else
        {
            logInfo(ruleId, "Restart the Timer!")
            timer.reschedule(now.plusSeconds(5))
        }

    ])

end

What i see in the logs:

2021-08-13 18:03:47.587 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing '<thing-UID>' changed from ONLINE to OFFLINE: Die Hue Bridge meldet, dass die Lampe nicht erreichbar ist.
2021-08-13 18:04:02.003 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'itmTestSwitch' received command OFF
2021-08-13 18:04:02.008 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'itmTestSwitch' changed from ON to OFF
2021-08-13 18:04:02.012 [INFO ] [g.openhab.core.model.script.TestRule] - Test rule started!
2021-08-13 18:04:02.016 [INFO ] [g.openhab.core.model.script.TestRule] - Thing is OFFLINE
2021-08-13 18:04:02.120 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:07.126 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:12.130 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:17.133 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:22.137 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:27.141 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:27.814 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing '<thing-UID>' changed from OFFLINE: Die Hue Bridge meldet, dass die Lampe nicht erreichbar ist. to ONLINE
2021-08-13 18:04:32.144 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!
2021-08-13 18:04:37.148 [INFO ] [g.openhab.core.model.script.TestRule] - Restarted the timer!

This goes on and on until i use the Testswitch again and it runs into problems…

2021-08-13 18:04:42.154 [WARN ] [ore.internal.scheduler.SchedulerImpl] - Scheduled job failed and stopped
java.lang.NullPointerException: cannot invoke method public abstract boolean org.openhab.core.model.script.actions.Timer.reschedule(java.time.ZonedDateTime) on null
  1. What did i do wrong?
    The if(thingStatus == "ONLINE") fires and so should the same if inside the timer.

  2. Is there any way to stop this never ending loop from the outside?

Thanks in advance!


  • Platform information:
    • Hardware: Raspberry Pi 4 Model B Rev 1.1
    • OS: Raspbian GNU/Linux 10 (buster)
    • Java Runtime Environment:
      • openjdk version “11.0.12” 2021-07-20 LTS
      • OpenJDK Runtime Environment Zulu11.50+19-CA (build 11.0.12+7-LTS)
      • OpenJDK Client VM Zulu11.50+19-CA (build 11.0.12+7-LTS, mixed mode)
    • openHAB version: 3.1.0 (Build)

You get the Thing status into a variable in the rule. There it stays, unchanging.
Every time the Timer code looks at that variable, it is unchanged.

You probably want to re-examine the Thing status in your Timer code.

Oh damn… that was the thing i was missing :frowning:
Thanks for the obvious hint!

Looks like i need to put that into a retriggering rule…
Or try to put the var into the timer.

The mistake made is a common one. Let’s look at the code line by line.

  1. The rule triggers when the Switch changes

  2. Initialize timer to null. But what if there is already a Timer running? This will orphan that existing timer which will continue to run forever and you’ll have no way to stop it short of reloading the rule.

  3. Acquire the current status of the Thing

  4. Log out the current status of the Thing

  5. If the Thing is ONLINE log something out. What about the Timer? Shouldn’t you cancel the Timer now too? You don’t need it any more. But there is another logic error here which I’ll get into momentarily.

  6. Whether or not the thing is online or not a new timer is created. You should only create the Timer when you need it.

  7. The timer’s lambda will run immediately. Inside the timer you check the value of thingStatus. But it could have been some time since thingStatus was populated. The Thing could have changed status since the rule was first run. You need to call getThingStatusInfo again inside the Timer’s lambda so you know what its status is right now, not what it was who knows how long ago.

The root problem for why the timer doesn’t stop is because you don’t check the status of the Thing again after starting the timer. Therefore the code inside the Timer doesn’t know the Thing changed to ONLINE and will run forever.

But as you can see above, that’s only one problem. Do you really want to create a Timer every time itmTestSwitch changes, no matter what it changes to? Maybe cancel the Timer when it changes to OFF.

Do you really want to blindly orphan existing Timers? No, instead check to see if timer !== null. If it is leave the existing timer running if the switch changed to ON. If the switch changed to OFF, cancel the timer and only then set it to null.

So the rule should look something like

val String ruleId = "TestRule"
var Timer timer = null

rule "Test"

when

    Item itmTestSwitch changed

then

    if(newState == OFF) {
        timer?.cancel() // this is a short way to say if(timer !== null) timer.cancel()
        timer = null
    }

    else if(newState == ON){
        if(timer === null){
            timer = createTimer(now, [ |
                // Check the status of the Thing inside the Timer
                var thingStatus = getThingStatusInfo('<Thing-UID>').getStatus().toString()
                logInfo(ruleId, "Thing is " + test)
                if(thingStatus == "ONLINE")
                {
                    timer = null
                    logInfo(ruleId, "Test rule executed!")
                }
                else
                {
                    logInfo(ruleId, "Restart the Timer!")
                    timer.reschedule(now.plusSeconds(5))
                }

            ])
        }
    }
end
2 Likes

Thanks for your explanation Rich!

It´s currently in my test rule to ensure it works before putting the logic into the correct rule.