[SOLVED] Question about how createTimer works

You would not need the second “MyItem.state==1” check because in the event that within your 100s your MyItem changes back to 1, the else statement would be executed.
In the else statement the first check

if(timer!=null) {

will check if the timer is actually running (i.e. it got started by the MyItem.state==1 trigger of the rule. If the timer is running (i.e. the 100s are not up yet), the

    			timer.cancel
    			timer = null

part will then stop the timer, in which case the code

                             logInfo("MyItem", "MyItem has been open for 100 seconds")

will never execute (even without the if statement above it).

Use that:


var Timer timer = null // AT THE TOP OF THE RULE FILE

rule "do something if item state is 0 for more than 10 seconds"
when
    Item MyItem changed
then
    if (MyItem.state == 0) {
        if (timer === null) {
            timer = createTimer(now.plusSeconds(10), [ |
                if (MyItem.state == 0) { // Item state is still 0 after 10s
                    // do something!
                }
            ])
        } else {
            if(timer!=null) {
                timer.cancel
                timer = null
       }
    }
end

Please note that if the item has changed from 0 to something else and then reverted back to 0 in the 10s the rule will still // do something!

The previous answers are all correct but I want to explain it a little more thoroughly.

This rule triggered whenever there is a change to MyItem.

So let’s say MyItem changes to 0. Then we create a timer that will start executing in 10 seconds to // do something.

Now let’s say it is 3 seconds later and MyItem changes to 1. The Rule starts running and the if(MyItem.state == 0) is false so the else clause runs. This clause cancels the Timer if it exists which means that // do something will never run.

Now let’s assume that MyItem never changes from 0 for the full ten seconds after the Timer is created. In that case // do something happens because MyItem never changed from the 0 state.

So as you can see, the keys to the behavior are:

  • The Rule runs for all changes to MyItem
  • If a Timer exists and MyItem changes to any state other than 0, the Timer gets cancelled

Thus, // do something will only run if MyItem remains in the 0 state for 10 seconds.

Thanks! Now I get it. I was getting stuck thinking the createTimer was working like the sleep function.

What I’m actually trying to accomplish is when a door closes if there is no motion for 5 minutes then turn a light on. I was trying to do it all in one rule but now I see I will use multiple rules and access the timer from different rules.

Do not access a timer from different rules. You will run into trouble. There is a better way to do this. I am on my phone right now but look for the design pattern for timer in the forum.

I’m not sure I would say that. I can think of lots of use cases where, for example, you may want to set a timer to prevent another Rule from running for a certain amount of time after another Rule ran.

However, I do agree that in this case I don’t see why one would need separate Rules.

@cshop, see Design Pattern: Motion Sensor Timer

1 Like

Hi I am also having some issues with create timer rules = I have followed the Design pattern thread and I now have lots of rules using createTimer. Should I use a separate variable for each timer at the top of the rule file? At the moment I am using createTimer without a variable eg:

rule “Wake Timmy and Dennis Monday to Friday”
when
Time cron "0 40 06 ? * MON-FRI "
then
if (Wakeup.state == ON)
{
logInfo(“WAKEUP.RULES”,“wakeup state is on so Dennis/Timmy alarm”)
sendCommand(Sonos_Shuffle, ON)
sendCommand(Sonos_Favorite,“Alarm”)
sendCommand(Sonos_Volume,15)
sendCommand(timmy_music,ON)
createTimer(now.plusSeconds(5), [ |
sendCommand(dennis_music,ON)
sendCommand(Lt_Dennis_Mezz,ON)
sendCommand(Lt_Timmy_Walls,ON)
])
}
else {
logInfo(“WAKEUP.RULES”,“wakeup state is off”)
}
end

I am having issues with rules not always firing (for example this one did not run this morning) and I can’t figure out why (even with logging).

Please use the code fences when posting code.

There is a stray space at the end of your cron string. That may be the reason.
This is your rule tidied up a bit.
I have used the sendCommand method instead of the action.
There is no issue with your use of the timer.

rule "Wake Timmy and Dennis Monday to Friday"
when
    Time cron "0 40 06 ? * MON-FRI"
then
    if (Wakeup.state == ON) {
        logInfo("WAKEUP.RULES", "wakeup state is on so Dennis/Timmy alarm")
        Sonos_Shuffle.sendCommand(ON)
        Sonos_Favorite.sendCommand("Alarm")
        Sonos_Volume.sendCommand(15)
        timmy_music.sendCommand(ON)
        createTimer(now.plusSeconds(5), [ |
            dennis_music.sendCommand(ON)
            Lt_Dennis_Mezz.sendCommand(ON)
            Lt_Timmy_Walls.sendCommand(ON)
        ])
    } else {
        logInfo("WAKEUP.RULES", "wakeup state is off")
    }
end

Thanks - sorry pressed the wrong markup button, getting old need some glasses!

Well done for spotting the space.

So I can have many createTimers running concurrently in different rules without problem and without using variables? What about having more createTimers within a rule so that I don’t send too many commands at once -
eg:

    Sonos_Shuffle.sendCommand(ON)
    createTimer(now.plusSeconds(5), [ |
    Sonos_Favorite.sendCommand("Alarm")
    Sonos_Volume.sendCommand(5)
    ])
    timmy_music.sendCommand(ON)
    createTimer(now.plusSeconds(15), [ |
    dennis_music.sendCommand(ON)
    Lt_Dennis_Mezz.sendCommand(ON)
    Lt_Timmy_Walls.sendCommand(ON)
    ])

I’ve been using sendcommand(item,action) all the time and I now understand the difference! Will change them all now!

That’s fine

1 Like

Hi I’d be grateful for some advice on the logic of createTimer. I have a rule which takes a speech to text input and then based on the txt containing content in a switch case construct actions are performed. I am trying to use using multiple timers for delays and seem to have the following issue which I don’t understand.

I am running the following environment:

Release = Raspbian GNU/Linux 9 (stretch)
Kernel = Linux 4.14.98-v7+
Platform = Raspberry Pi 3 Model B Rev 1.2
openHAB 2.5.0~M1-1 (Milestone Build)

The following is an extract of a block in the rule which doesn’t work, but in my opinion looks more logical then the one further below which works fine (I ma trying to release timers to avoid increasing memory footprint):

		case txt.contains("bedtime"): {
			lou_onkrec_01_har.sendCommand("PowerOff")
			if (timer === null) {
				timer = createTimer (now.plusSeconds(4)) [|
					Sonoff_3_Switch.sendCommand(OFF)
					lounge_floor_light.sendCommand(OFF)
					hall_floor_light.sendCommand(ON)
				]
			}	
			timer.cancel
			timer = null
			if (timer === null) {
				timer = createTimer (now.plusSeconds(8)) [|
					lounge_ceiling_light.sendCommand(OFF)
					bedroom_ceiling_light.sendCommand(30)
					all_media.sendCommand(OFF)
				]
			}
			timer.cancel
			timer = null
		}

The above rule performs the first command (lou_onkrec_01_har.sendCommand(“PowerOff”)) and then exits the case block.

The next attempt works fine but looks messy and I’m worried that the timers are not released…

case txt.contains("bedtime"): {
			lou_onkrec_01_har.sendCommand("PowerOff")
			timer = createTimer (now.plusSeconds(4)) [|
				Sonoff_3_Switch.sendCommand(OFF)
				lounge_floor_light.sendCommand(OFF)
				hall_floor_light.sendCommand(ON)
				timer = createTimer (now.plusSeconds(8)) [|
					lounge_ceiling_light.sendCommand(OFF)
					bedroom_ceiling_light.sendCommand(30)
					all_media.sendCommand(OFF)
				]
			]
			timer.cancel
		}

Also I receive the following errors in my log file:

019-02-25 23:14:36.958 [ome.event.ItemCommandEvent] - Item 'VoiceCommand' received command bedtime

2019-02-25 23:14:36.974 [vent.ItemStateChangedEvent] - VoiceCommand changed from  to bedtime

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:36.990 [INFO ] [se.smarthome.model.script.Voice.Rec:] - VoiceCommand received: voicecommand (type=stringitem, state=bedtime, label=command, category=null, groups=[all])

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:37.008 [ome.event.ItemCommandEvent] - Item 'lou_onkrec_01_har' received command PowerOff

2019-02-25 23:14:37.103 [nt.ItemStatePredictedEvent] - lou_onkrec_01_har predicted to become PowerOff

2019-02-25 23:14:37.109 [vent.ItemStateChangedEvent] - lou_onkrec_01_har changed from UNDEF to PowerOff

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:37.114 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'VoiceControl': An error occurred during the script execution: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1::0::/1)

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:37.130 [vent.ItemStateChangedEvent] - lou_onkrec_01_har changed from PowerOff to UNDEF

2019-02-25 23:14:41.014 [ome.event.ItemCommandEvent] - Item 'Sonoff_3_Switch' received command OFF

2019-02-25 23:14:41.026 [ome.event.ItemCommandEvent] - Item 'lounge_floor_light' received command OFF

2019-02-25 23:14:41.043 [ome.event.ItemCommandEvent] - Item 'hall_floor_light' received command ON

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:41.040 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2019-02-25T23:14:41.007+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <null>.timer = <XFeatureCallImplCustom>

} ] threw an unhandled Exception: 

java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1.7.6.1.1.0.3::0::/1)

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]

	at com.sun.proxy.$Proxy181.apply(Unknown Source) ~[?:?]

	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]

	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:42.845 [vent.ItemStateChangedEvent] - lounge_floor_light changed from ON to OFF

2019-02-25 23:14:42.849 [vent.ItemStateChangedEvent] - hall_floor_light changed from OFF to ON

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:42.848 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2019-02-25T23:14:41.007+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <null>.timer = <XFeatureCallImplCustom>

} ] threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception.

	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

Caused by: java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1.7.6.1.1.0.3::0::/1)

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]

	at com.sun.proxy.$Proxy181.apply(Unknown Source) ~[?:?]

	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]

	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]

	... 1 more

2019-02-25 23:14:49.034 [ome.event.ItemCommandEvent] - Item 'lounge_ceiling_light' received command OFF

2019-02-25 23:14:49.040 [ome.event.ItemCommandEvent] - Item 'bedroom_ceiling_light' received command 30

2019-02-25 23:14:49.045 [ome.event.ItemCommandEvent] - Item 'all_media' received command OFF

2019-02-25 23:14:49.069 [ome.event.ItemCommandEvent] - Item 'bathroom_washmachine_switch' received command OFF

2019-02-25 23:14:49.073 [ome.event.ItemCommandEvent] - Item 'Sonoff_3_Switch' received command OFF

2019-02-25 23:14:49.079 [ome.event.ItemCommandEvent] - Item 'lounge_tv' received command OFF

2019-02-25 23:14:49.085 [ome.event.ItemCommandEvent] - Item 'kitchen_music' received command OFF

2019-02-25 23:14:49.094 [ome.event.ItemCommandEvent] - Item 'lounge_music' received command OFF

2019-02-25 23:14:49.101 [nt.ItemStatePredictedEvent] - lounge_ceiling_light predicted to become OFF

2019-02-25 23:14:49.118 [nt.ItemStatePredictedEvent] - bedroom_ceiling_light predicted to become 30

2019-02-25 23:14:49.163 [vent.ItemStateChangedEvent] - lounge_ceiling_light changed from 40 to 0

2019-02-25 23:14:49.167 [vent.ItemStateChangedEvent] - bedroom_ceiling_light changed from 0 to 30

2019-02-25 23:14:49.175 [nt.ItemStatePredictedEvent] - kitchen_music predicted to become OFF

2019-02-25 23:14:49.183 [nt.ItemStatePredictedEvent] - lounge_music predicted to become OFF

I’d be grateful for any insight into this.

Cheers

Vincent

			if (timer === null) {
				timer = createTimer (now.plusSeconds(4)) [|
					...
				]
			}	
			timer.cancel
			timer = null
			if (timer === null) {
				timer = createTimer (now.plusSeconds(8)) [|
					...
				]
			}
			timer.cancel
			timer = null

This code creates a timer block scheduled for future execution, then immediately cancels it and destroys it.
It then reuses the same handle to create and schedule a timer block, and immediately cancels and destroys it again.

For clarity; you’re using a variable named timer as a container to hold a timer. That’s often useful, if you want to check or change that timer later. It works like a handle so you get hold of that scheduled code.

The action of createTimer is to make a block of code and schedule it to be run later. That’s the bit between [ ] above.
Having scheduled that, we then immediately carry on to the next line in the rest of the rule, and execute that.
In this case that’s timer.cancel, which has the obvious effect and stops that schedule.

Everything rossko57 said is dead on right.

createTimer schedules a bit of code (stuff between the [ ]) to run sometime in the future without blocking.

To block, you would use Thread::sleep. However, there are lots of reasons why that’s a bad idea so avoid using it if at all possible.

That is why your second attempt works. You schedule the code to run in 4 seconds. After 4 seconds it sends the OFF commands then you schedule another timer to run in 8 seconds ti send the last three commands.

NOTE: You should remove the timer.cancel. Hmmmm, are you sure this works? With the cancel it should prevent it from running.

So, the question is do you ever want to cancel these timers based on some other event? Or when the txt contains bedtime it will always run these Timers no matter what. If it’s the former I recommend creating two global variables to hold the timers and then schedule the two separately.

firstTimer = createTimer(now.plusSeconds(4), [ |
    Sonoff_3_Switch...
   firstTimer = null
])
secondTimer = createTimer(now.plusSeconds(12), [ | // 8+4
    lounge_ceiling_light...
    secondTimer = null
])

I doubt the errors you are seeing are coming from the block of code you posted.

Finally, I recommend reviewing A simple scene management: finally a good use of scripts for a potential way to better organize your scenes. I suspect this one Rule is HUGE. This might give you a way to make it more manageable and maintainable.

Many thx to you and and to rlkoshak for helping me understand this better and for your quick responses, I now see where my fault lies. Thanks also for the pointer to scene management as I think you are right it will help me manage this better :sunglasses::+1:

Hello!
I build a timer rule and it does not work as I want. I´ve got no ideas. Perhaps somebody can help me. The rule “bewegungsmelder an” works fine. If the rule is triggerd, for example three times, counter_1 shows in the logfiles “3”. After 10 seconds counter_1 is “0”. But the rule “bewegungsmelder aus” do not cancel the timer. If the rule “bewegungsmelder aus” is triggerd during 10 seconds, counter_1 is after 10 seconds “0”. The timer “timer_bewegungsmelder” in rule “bewegungsmelder an” will be not canceld. Where is my mistake??

var Number counter_1 = 0
var Timer timer_bewegungsmelder = null

rule "bewegungsmelder an"

when
Item Licht_EG_Wohnzimmer_WL_2 changed
then  
            logInfo("bewegungsmelder", "counter_1_vor= " + counter_1)
            counter_1 = counter_1 + 1
            timer_bewegungsmelder = createTimer(now.plusSeconds(10), [|
	    counter_1 = 0                 
            timer_bewegungsmelder = null 
            logInfo("bewegungsmelder", "counter_1_nach= " + counter_1)  
            ])          
end


rule "bewegungsmelder aus"

when
Item Licht_EG_Wohnzimmer_WL_1 changed
then  
        timer_bewegungsmelder.cancel ()  
        timer_bewegungsmelder = null
        logInfo("bewegungsmelder", "counter_1_aus= " + counter_1)                         
end

Every time rule “bewegungsmelder an” runs, it creates a new timer to run in 10 seconds time.
Every time.
A “handle” for the newly created timer is placed in your variable timer_bewegungsmelder
BUT
that on its own does not cancel any timer that variable already points to. You’ve just lost your handle, there is nothing you can do about it now.

Example -
rule triggers, 10s timer A is started. Handle points to timer A

5 secs later, rule might be triggered again. New timer B is created for 10s time. Handle updated to point to timer B. (No further control is possible over timer A, because the handle is lost)

5 secs after that, Timer A executes and sets the handle to null (No further control over timer B is possible, because the handle is lost)

5 secs after that, Timer B executes.

Look again at previous examples in this thread, and study rules that test for a null handle before creating a new timer.
At that time you can reschedule an existing timer or cancel it.

Hello rossko57,
so many thanks to you! Perfect explanation! Now I see my Problem/ mistake. I will start a new approach!!!

I have a timer like so:

var Timer officeLightTimer = null
var timeoutMinutes = 10

rule "Office Motion Detected"
    when
        Item x10_A4_time changed
    then
        logInfo("rules", "x10_A4_time changed")
        if(officeLightTimer === null){
            Office_Light.sendCommand(ON)
            logWarn("rules", "timer is null - creating new timer")
            officeLightTimer = createTimer(now.plusMinutes(timeoutMinutes), [|
                logWarn("rules", "timer executed ...")
                Office_Light.sendCommand(OFF)
                if(officeLightTimer != null){
                    logWarn("rules", "timer not null - making it so ...")
                    officeLightTimer = null
                } else {
                    logWarn("rules", "timer is null already - do nothing ...")
                }
            ])
        } else {
            logInfo("rules", "officeLightTimer not null ...")
            logInfo("rules", "... so rescheduling ...")
            officeLightTimer.reschedule(now.plusMinutes(timeoutMinutes))
        }
end

I’m finding when this runs and x10_A4_time changes, rescheduling occurs as expected. When the changes stop (i.e. the room is not occupied), I get this error thrown:

2020-03-02 07:12:32.043 [WARN ] [eclipse.smarthome.model.script.rules] - timer not null - making it so ...
2020-03-02 10:28:08.737 [ERROR] [ersey.server.ServerRuntime$Responder] - An I/O error has occurred while writing a response message entity to the container output stream.
...
Caused by: org.eclipse.jetty.io.EofException
...
Caused by: java.io.IOException: Broken pipe

I’ve added the “if(officeLightTimer != null){ …” because I was getting a null pointer error by assigning the pointer to null. Should I be using timer.cancel instead of timer.null or is there another issue here?

This needs to be !== null, but I doubt this is the source of the issue you mention.

Which “error” do you mean?

There’s no error there. You write that WARN message yourself from your own timer code.
It is to be expected that your handle variable officeLightTimer is pointing at your timer (i.e. not null) while your timer is running, while your timer is executing, and indeed for ever after. Unless and until you have code that sets it to null again.
Maybe you are thinking it somehow automatically gets set null after the timer triggers - it doesn’t. It’s just a variable, completely under your control.

This message, from many hours later, is usually about some kind of UI incident. A tablet or browser has been disconnected, or similar.