Light on Motion Rule - Need to understand

I read many posts regarding this famous rule and try to understand and improve my motion rule.
Most of the posts are old and this design pattern is deprecated.

I have two main lights in my office room. Both of them are grouped and shall switch on when the movement will be recognized but only if brightness will be less then given value.

Group:Switch:OR(ON, OFF)    gLightMainOffice                                          "Light"                                          <light>              (Home)                                                           ["Lighting"]

Switch                      BS_Office_Main_Light_1_Switch                             "Light Gosia [%s]"                               <light>              (BS_Office, gLight, gLightOffice, gLightMainOffice)              ["Lighting"]                        {channel="zwave:device:8985008e:node39:switch_binary1"}
Switch                      BS_Office_Main_Light_2_Switch   
var Timer motionDelayTimer = null
val int timeoutMinutes = 7
val int OfficeBrightness = 25

rule "Office Motion Sensor ON" 

when 
    Item BS_Office_Motion changed from OFF to ON 
then
    logInfo("Motion Office", "Luminance in Office: "+ BS_Office_Luminance.state.toString)
    if(motionDelayTimer === null || motionDelayTimer.hasTerminated) {
        motionDelayTimer = null
        gLightMainOffice.sendCommand(ON)
        logInfo("Motion Office", "Movement recognised -> Light ON")
    }
    else {
        logInfo("Motion Office", "Movement recognised -> Timer restart.")
        motionDelayTimer.cancel
        motionDelayTimer = null
    }
end

rule "Office Motion Sensor OFF"

when
    Item BS_Office_Motion changed from ON to OFF
then
    logInfo("Motion Office", "No more movement -> Timer start.")
    motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
    motionDelayTimer.cancel
    motionDelayTimer = null
    gLightMainOffice.sendCommand(OFF)
    logInfo("Motion Office", "Timer elapsed -> Light OFF")
    ])

end

This rule works without luminance check.
1. I do not understand what is the difference between cancel and “resetting” the Timer.

        motionDelayTimer.cancel
        motionDelayTimer = null

2. I do not understand this statement. I can only suspect it means
if Timer is not equal null OR has been terminated. What does it mean terminated in this case?

(motionDelayTimer === null || motionDelayTimer.hasTerminated)

3. How and where to implement condition for the brightness?
this one does not work:

var int Luminance = (BS_Office_Luminance.state as DecimalType).intValue
...
    if((motionDelayTimer === null || motionDelayTimer.hasTerminated) && Luminance < OfficeBrightness) {
        motionDelayTimer = null
        gLightMainOffice.sendCommand(ON)
        logInfo("Motion Office", "Movement recognised -> Light ON")
    }

But notice that the post links to a rule template in the marketplace. It’s deprecated because you don’t need to code this any more. Just install and configure the Open Door rule template as described.

The rule you create that gets called by Open Door would be a simple:

if(BS_Office_Luminance.state < 25) {
    gLightMainOffice.sendCommand(ON)
}

That’s simple enough you could create it in the UI without any code at all. So with the rule template you can achieve this without any coding. That’s why the design pattern is deprecated. You don’t need to code this yourself anymore.

As written it’s a little redundant. The cancel stops the timer from running at the scheduled time. Setting it to null clears the reference so the timer can be garbage collected later.

If the timer is null or if the timer has already run (i.e. the scheduled time has passed and the passed in timer function was called) or it’s been cancelled. See Actions | openHAB for a brief explanation of all the Timer methods.

What’s

If I were in the UI, I’d set a simple rule condition. In Rules DSL I’d do this at the top of the rule:

    if( BS_Office_Luminance.state < OfficeBrightness ) {
        return;
    }

Note, it’s a bad idea to specify the types of variables unless you really need to and you should avoid primitives except were absolutely necessary in Rules DSL.

Thank you Rich for your answer. I can always count on you :wink:
For me it is important to check if someone is in the room and only if there is no movement for a while lights shall be switched off. Maybe I am making this too complicated.

I forgot to write I am still in the old, working 2.5.12 version of the openHab based on Raspberry Pi so I am not sure if I can write the rule using UI

Can you elaborate on this? Is it sth specific for OH3?

Would it be better to define that way?

var timeoutMinutes = 7
var OfficeBrightness = 25

You can to some extent, but I would not recommend it at all. It would just be making it difficult for you, when there are lots of DSL examples to use in OH2
You certainly cannot use marketplace rule templates directly.

This is what i was thinking…
I would appreciate support on the DSL Rule and its extension to create dependency on the brightness of the room.

I understood the difference between timer.cancel and timer=null

I have difficulties to structure the code introducing the third condition. Somehow it does not work and I have some errors in the log…

You could but it’s probably not worth it.

But in that case, all the stuff that is deprecated below where I talk about the rule template will still apply. I only marked it deprecated because I don’t intent to keep updating the examples as OH grows and breaking changes creep in. But I also didn’t delete it.

Yes, rule templates are an OH 3 only thing. But what it means is you can install a rule template just like you do and add-on.

Then instantiate and configure a rule based on that template.

And you are good to go. No code required.

Yes. Only specify the type if you have null to the right of the =. In that case there isn’t enough context to know what type the variable will be.

Never ever ever withhold stuff like that. It’s like going to the doctor and neglecting to say your arm hurts.

So what are these error logs?

Again, don’t specify the type when you don’t need to and only use primitives when absolutely necessary.

var Luminance = BS_Office_Luminance.state as Number

Something you’d really like to know, what is that value exactly?
Look in your events.log, or log the value out of your rule and look at the message in your openhab.log.
You’ll find those logs invaluable when creating rules in OH2

What you might find is that the value has units of measurement, and the comparison is not working as you expect.

I have been trying to add the condition in the rule. Everything should remain as above but only if defined brightness level and one switch button=ON will be met. I can not manage timers having exceptions. Can someone help with the code? I really struggle with thi

var Timer motionDelayTimer = null
val int OfficeBrightness = 25
val int timeoutMinutes = 2
var ConditionFlag = null


rule "Office Motion Sensor ON" 

when 
    Item BS_Office_Motion changed from OFF to ON  

then
    var Luminance = BS_Office_Luminance.state as Number
    logInfo("Motion Office", "Luminance in Office: "+ BS_Office_Luminance.state.toString)
    logInfo("Motion Office", "Rule: " + OfficeMainLightRule.state)
            
    if ((Luminance < OfficeBrightness) && (OfficeMainLightRule.state == ON)){
        ConditionFlag = true
        if(motionDelayTimer === null || motionDelayTimer.hasTerminated){
           motionDelayTimer = null
           gLightMainOffice.sendCommand(ON)
           logInfo("Motion Office", "Movement recognised -> Light ON")
        }
        else {
            logInfo("Motion Office", "Movement recognised -> Timer restart.")
            motionDelayTimer.cancel
            motionDelayTimer = null
        }
    }
    else {
        logInfo("Motion Office", "Movement recognised but conditions not met")
    }
end

rule "Office Motion Sensor OFF"

when
    Item BS_Office_Motion changed from ON to OFF
then
    if (ConditionFlag){
        logInfo("Motion Office", "No more movement -> Timer start.")
        motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
        motionDelayTimer.cancel
        motionDelayTimer = null
        gLightMainOffice.sendCommand(OFF)
        logInfo("Motion Office", "Timer elapsed -> Light OFF")
        ])
    }
    else{
        logInfo("Motion Office", "ConditionFlag not set.")  
        gLightMainOffice.sendCommand(OFF)
        ConditionFlag = null
    }
end

Errors:

2022-11-17 12:55:03.081 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.Timer 259 2022-11-17T12:55:03.078+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  <XFeatureCallImplCustom>.cancel
  <null>.motionDelayTimer = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
  logInfo(<XStringLiteralImpl>,<XStringLiteralImpl>)
} ] threw an unhandled Exception: 

java.lang.NullPointerException: null
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
	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.$Proxy855.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]

2022-11-17 12:55:03.127 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.Timer 259 2022-11-17T12:55:03.078+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XFeatureCallImplCustom>.cancel
  <null>.motionDelayTimer = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
  logInfo(<XStringLiteralImpl>,<XStringLiteralImpl>)
} ] threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]

Caused by: java.lang.NullPointerException
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
	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.$Proxy855.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more

You’ve not shown us any of the useful loginfo messages your rules generated before that, that would show us what it had been doing.

Beware of “orphan” timers.
createTimer() does what it says, it sets up a block of code (the stuff between ) to be executed in the future. That’s tucked away in the system, and the only way you can communicate with it later is by using the handle or pointer it gives you.
Thats the

motionDelayTimer = createTimer( ...

part, motionDelayTimer is just the handle, not the whole timer.

When you edit a rule file, it gets reloaded.
When you reload the rule file, it executes

You’ve just destroyed the handle, if it existed.

That does not stop any timer that is already in the schedule..

So if you run a rule that starts a timer for two minutes in the future, and then edit the rule after one minute … you’ve got no way to stop or cancel that timer. It’s still going to run after a further minute.
When these “orphan” timers run, they almost always fail with a null error, because reloading the parent rule has destroyed a lot of the context the timer could would normally have.

The bottom line is, you can’t do anything about that, apart from ignore editing related errors if they happen within the time that you expect.
It’s just a nuisance, but is of course confusing if you are looking for real errors.

  • Don’t cancel a timer inside the timer’s body. It’s redundant (the timer has already canceled) and causes problems.
  • Avoid resetting the timer variable back to null until the end of the timer function. It should be the last line executed.
  • Use indentation every time you create a new context. Every line after a (, [, or { should be indented until the matching closing ), ], or }.
  • If your rule has log statements and throws an error show us those log statements too. Don’t try to help us by redacting information.
  • What’s the purpose of ConditionFlag?

I wanted to recognise the condition (Brightness and Switch) and deal with the state when the Motion will go from ON to OFF. Otherwise, the timer will star if the Motion will change from ON to OFF even if conditions are not met.

Before going into code changes I wish to understand this line as well. What does
"[ |

| ]
mean? How to read the code from Timer creation? I understand this this way:
create the timer which counts from now on up to timeoutMinutes
but I do not understand conditions of this timer taken in the

        motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
        motionDelayTimer.cancel
        motionDelayTimer = null
        gLightMainOffice.sendCommand(OFF)
        logInfo("Motion Office", "Timer elapsed -> Light OFF")
        ])

I will capture more next time. You are right. I just wanted to check if the error is coming from timer. But I am 99% sure it is.

The basic form

createTimer( Some_Future_Time, Some_Code_to_Execute )

To define the block of code to execute we use , and there’s a | just because that’s the way it works

createTimer( Some_Future_Time, [ | Some_Code_to_Execute ] )

// or a little more readably

createTimer( Some_Future_Time, [ |
Some_Code_to_Execute
]  // ends the block of code to run later
) // ends the createTimer definition.
Next_Stuff // not part of timer

To see the value of indentation

createTimer( Some_Future_Time, [ |
      Some_Code_to_Execute
      ]
 )
Some_Other_Code

When this runs, createTimer sets up its schedule to do something in the future - the indented code.
Then the Some_Other_Code is immediately executed, and the rule probably exits later.

At some later time, the block of code in gets executed.

Many thanks! Understood.

For this explanation that’s fine.

But in general and to be complete, the | separates the arguments from the code. createTimer can’'t handle a function with arguments like that so for Timers it will always [ | ] with nothing before the |, meaning no arguments are passed.

More detail is discussed in Reusable Functions: A simple lambda example with copious notes

Ok I modified my code but still have problems with timer. This time it happens when rule from ON to OFF
My intention is to react only if the brightness is below 25 and rule switch is on. If these conditions will change during the timer run. (Someone manipulates lights switches or light will change) it shall still switch the lights off after timer expires.

var Timer motionDelayTimer = null
val int timeoutMinutes = 1
val int OfficeBrightness = 25

rule "Office Motion Sensor ON" 

when 
    Item BS_Office_Motion changed from OFF to ON 
then
    var Luminance = BS_Office_Luminance.state as Number

    if((motionDelayTimer === null || motionDelayTimer.hasTerminated) && (Luminance < OfficeBrightness) && (OfficeMainLightRule.state == ON)) {
        motionDelayTimer = null
        gLightMainOffice.sendCommand(ON)
        logInfo("Motion Office", "Movement recognised -> Light ON")
    }
    else {
        logInfo("Motion Office", "Movement recognised -> Timer restart.")
        motionDelayTimer.cancel
        motionDelayTimer = null
    }
end

rule "Office Motion Sensor OFF"

when
    Item BS_Office_Motion changed from ON to OFF
then
        logInfo("Motion Office", "No more movement -> Timer start.")
        motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
        motionDelayTimer.cancel
        motionDelayTimer = null
        gLightMainOffice.sendCommand(OFF)
        logInfo("Motion Office", "Timer elapsed -> Light OFF")
        ])
end

Log file (this time from the beginning of the rule refresh)

2022-11-20 18:38:15.469 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'lightOffice.rules'
==> /var/log/openhab2/events.log <==
2022-11-20 18:38:20.491 [vent.ItemStateChangedEvent] - FF_Bathroom_Temperature changed from 21.2 to 21.3
2022-11-20 18:38:29.645 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from OFF to ON
2022-11-20 18:38:29.651 [GroupItemStateChangedEvent] - gMotion changed from OFF to ON through BS_Office_Motion
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:38:30.446 [INFO ] [smarthome.model.script.Motion Office] - Movement recognised -> Light ON
==> /var/log/openhab2/events.log <==
2022-11-20 18:38:30.447 [ome.event.ItemCommandEvent] - Item 'gLightMainOffice' received command ON
2022-11-20 18:38:30.460 [ome.event.ItemCommandEvent] - Item 'BS_Office_Main_Light_1_Switch' received command ON
2022-11-20 18:38:30.486 [ome.event.ItemCommandEvent] - Item 'BS_Office_Main_Light_2_Switch' received command ON
2022-11-20 18:38:30.514 [nt.ItemStatePredictedEvent] - BS_Office_Main_Light_1_Switch predicted to become ON
2022-11-20 18:38:30.531 [nt.ItemStatePredictedEvent] - BS_Office_Main_Light_2_Switch predicted to become ON
2022-11-20 18:38:30.546 [GroupItemStateChangedEvent] - gLightOffice changed from OFF to ON through BS_Office_Main_Light_1_Switch
2022-11-20 18:38:30.549 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_1_Switch changed from OFF to ON
2022-11-20 18:38:30.553 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_2_Switch changed from OFF to ON
2022-11-20 18:38:32.145 [GroupItemStateChangedEvent] - gPower changed from 8.98 to 9.46 through BS_Office_Main_Light_EMeterW
2022-11-20 18:38:32.152 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_EMeterW changed from 0 to 10.7
2022-11-20 18:38:32.299 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_1_EMeterW changed from 0 to 10.7
2022-11-20 18:38:32.305 [GroupItemStateChangedEvent] - gPower changed from 9.46 to 9.95 through BS_Office_Main_Light_1_EMeterW
2022-11-20 18:38:32.448 [GroupItemStateChangedEvent] - gPower changed from 9.95 to 10.44 through BS_Office_Main_Light_2_EMeterW
2022-11-20 18:38:32.451 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_2_EMeterW changed from 0 to 10.9
2022-11-20 18:38:35.386 [GroupItemStateChangedEvent] - gLuminance changed from 7 to 12 through BS_Office_Luminance
2022-11-20 18:38:35.390 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 5 to 27
2022-11-20 18:38:39.567 [event.ThingStatusInfoEvent] - 'tradfri:gateway:gw449160262b3d' updated: ONLINE
2022-11-20 18:38:39.576 [event.ThingStatusInfoEvent] - 'tradfri:gateway:gw449160262b3d' updated: ONLINE
2022-11-20 18:38:40.331 [vent.ItemStateChangedEvent] - RaspberryPiCpuLoad1 changed from 0.4 to 0.2
2022-11-20 18:38:40.340 [vent.ItemStateChangedEvent] - RaspberryPiUptime changed from 7132.8 to 7134.8
2022-11-20 18:38:50.502 [vent.ItemStateChangedEvent] - FF_Bathroom_Temperature changed from 21.3 to 21.2
2022-11-20 18:39:00.062 [vent.ItemStateChangedEvent] - RaspberryPiUptimeFormatted changed from 4days | 22hrs | 52min to 4days | 22hrs | 54min
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:39:00.069 [INFO ] [.smarthome.model.script.system.rules] - Uptime updated to 4days | 22hrs | 54min
==> /var/log/openhab2/events.log <==
2022-11-20 18:39:20.276 [vent.ItemStateChangedEvent] - RaspberryPiSensorsCpuTemp changed from 40.8 to 40.2
2022-11-20 18:39:20.319 [vent.ItemStateChangedEvent] - RaspberryPiMemoryAvailablePercent changed from 30.4 to 30.6
2022-11-20 18:39:20.481 [vent.ItemStateChangedEvent] - FF_Bathroom_Temperature changed from 21.2 to 21.3
2022-11-20 18:39:21.669 [GroupItemStateChangedEvent] - gMotion changed from ON to OFF through BS_Office_Motion
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:39:21.670 [INFO ] [smarthome.model.script.Motion Office] - No more movement -> Timer start.
==> /var/log/openhab2/events.log <==
2022-11-20 18:39:21.679 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from ON to OFF
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:39:29.327 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.Timer 629 2022-11-20T18:39:29.324+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XFeatureCallImplCustom>.cancel
  <null>.motionDelayTimer = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
  logInfo(<XStringLiteralImpl>,<XStringLiteralImpl>)
} ] threw an unhandled Exception: 
java.lang.NullPointerException: null
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
	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.$Proxy855.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
2022-11-20 18:39:29.380 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.Timer 629 2022-11-20T18:39:29.324+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XFeatureCallImplCustom>.cancel
  <null>.motionDelayTimer = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
  logInfo(<XStringLiteralImpl>,<XStringLiteralImpl>)
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [bundleFile:?]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
Caused by: java.lang.NullPointerException
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:65) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:140) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:991) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:954) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:235) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:857) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
	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.$Proxy855.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more
==> /var/log/openhab2/events.log <==
2022-11-20 18:39:39.573 [event.ThingStatusInfoEvent] - 'tradfri:gateway:gw449160262b3d' updated: ONLINE
2022-11-20 18:39:39.592 [event.ThingStatusInfoEvent] - 'tradfri:gateway:gw449160262b3d' updated: ONLINE
2022-11-20 18:39:51.981 [vent.ItemStateChangedEvent] - FF_Bathroom_Temperature changed from 21.3 to 21.2
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:40:00.070 [INFO ] [.smarthome.model.script.system.rules] - Uptime updated to 4days | 22hrs | 54min
2022-11-20 18:40:20.327 [vent.ItemStateChangedEvent] - RaspberryPiSensorsCpuTemp changed from 40.2 to 40.8
2022-11-20 18:40:20.372 [vent.ItemStateChangedEvent] - RaspberryPiMemoryAvailablePercent changed from 30.6 to 30.5
2022-11-20 18:40:21.447 [vent.ItemStateChangedEvent] - LocalSunAzimuth changed from 261.1373037695782 ° to 262.0342903763901 °
2022-11-20 18:40:21.451 [vent.ItemStateChangedEvent] - LocalSunElevation changed from -18.717121720215456 ° to -19.53326343581167 °
2022-11-20 18:40:21.685 [ome.event.ItemCommandEvent] - Item 'gLightMainOffice' received command OFF
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:40:21.685 [INFO ] [smarthome.model.script.Motion Office] - Timer elapsed -> Light OFF
2022-11-20 18:41:03.835 [INFO ] [smarthome.model.script.Motion Office] - Movement recognised -> Timer restart.
2022-11-20 18:41:03.838 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Office Motion Sensor ON': cannot invoke method public abstract boolean org.eclipse.smarthome.model.script.actions.Timer.cancel() on null
==> /var/log/openhab2/events.log <==
2022-11-20 18:41:20.377 [vent.ItemStateChangedEvent] - RaspberryPiSensorsCpuTemp changed from 40.8 to 40.2
2022-11-20 18:41:26.374 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 6 to 5
2022-11-20 18:41:34.412 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from ON to OFF
2022-11-20 18:41:34.416 [GroupItemStateChangedEvent] - gMotion changed from ON to OFF through BS_Office_Motion
==> /var/log/openhab2/openhab.log <==
2022-11-20 18:41:34.421 [INFO ] [smarthome.model.script.Motion Office] - No more movement -> Timer start.`

As Rich already said, there’s no point in trying to cancel a Timer within the block of code that the Timer is executing. You can’t cancel it if it is already happening. It’s clearly nonsense.

Meantime, in the other rule

It is possible to arrive here when some other rule section, or the timer itself, has set motionDelayTimer to null…
That will result in trying to cancel null, which is an error.

So don’t cancel it if it is null.
Luckily there is shorthand way to do that
motionDelayTimer?.cancel

Despite what your logInfo says, this bit of code does not do any restarting or rescheduling, is it supposed to?

Fix those and see what you are left with.

Sorry. I forgot to comment this line.
Now it seems the timer errors are not appearing but whenever the condition (Brightness and Rule Switch) are not met timer is being cancelled and one more time changed to null if the motion goes from ON to OFF. My initial plan was to place a flag when condition is being met and in the state from ON to OFF take this under consideration but maybe i am complicating this code too much

var Timer motionDelayTimer = null
val int timeoutMinutes = 1
val int OfficeBrightness = 25

rule "Office Motion Sensor ON" 

when 
    Item BS_Office_Motion changed from OFF to ON 
then
    var Luminance = BS_Office_Luminance.state as Number

    if((motionDelayTimer === null || motionDelayTimer.hasTerminated) && (Luminance < OfficeBrightness) && (OfficeMainLightRule.state == ON)) {
        motionDelayTimer = null
        gLightMainOffice.sendCommand(ON)
        logInfo("Motion Office", "Movement recognised -> Light ON")
    }
    else {
        logInfo("Motion Office", "Movement recognised -> Timer restart.")
        motionDelayTimer?.cancel
    }
end

rule "Office Motion Sensor OFF"

when
    Item BS_Office_Motion changed from ON to OFF
then
        logInfo("Motion Office", "No more movement -> Timer start.")
        motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
        motionDelayTimer = null
        gLightMainOffice.sendCommand(OFF)
        logInfo("Motion Office", "Timer elapsed -> Light OFF")
        ])
end
2022-11-21 17:26:00.996 [ome.event.ItemCommandEvent] - Item 'OfficeMainLightRule' received command ON
2022-11-21 17:26:01.015 [vent.ItemStateChangedEvent] - OfficeMainLightRule changed from OFF to ON
2022-11-21 17:26:05.889 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from OFF to ON
2022-11-21 17:26:05.930 [ome.event.ItemCommandEvent] - Item 'gLightMainOffice' received command ON
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:26:05.930 [INFO ] [smarthome.model.script.Motion Office] - Movement recognised -> Light ON
==> /var/log/openhab2/events.log <==
2022-11-21 17:26:05.949 [ome.event.ItemCommandEvent] - Item 'BS_Office_Main_Light_1_Switch' received command ON
2022-11-21 17:26:05.983 [ome.event.ItemCommandEvent] - Item 'BS_Office_Main_Light_2_Switch' received command ON
2022-11-21 17:26:06.011 [nt.ItemStatePredictedEvent] - BS_Office_Main_Light_1_Switch predicted to become ON
2022-11-21 17:26:06.027 [nt.ItemStatePredictedEvent] - BS_Office_Main_Light_2_Switch predicted to become ON
2022-11-21 17:26:06.043 [GroupItemStateChangedEvent] - gLightOffice changed from OFF to ON through BS_Office_Main_Light_1_Switch
2022-11-21 17:26:06.046 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_1_Switch changed from OFF to ON
2022-11-21 17:26:06.051 [vent.ItemStateChangedEvent] - BS_Office_Main_Light_2_Switch changed from OFF to ON
2022-11-21 17:26:08.301 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 4 to 24
2022-11-21 17:26:18.376 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 24 to 25
2022-11-21 17:26:28.460 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 25 to 26

2022-11-21 17:26:36.498 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from ON to OFF
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:26:36.503 [INFO ] [smarthome.model.script.Motion Office] - No more movement -> Timer start.
==> /var/log/openhab2/events.log <==
2022-11-21 17:26:44.206 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from OFF to ON
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:26:44.225 [INFO ] [smarthome.model.script.Motion Office] - Movement recognised -> Timer restart.
==> /var/log/openhab2/events.log <==
2022-11-21 17:26:48.597 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 26 to 25
2022-11-21 17:26:59.648 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 25 to 26
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:27:00.084 [INFO ] [.smarthome.model.script.system.rules] - Uptime updated to 5days | 21hrs | 41min
==> /var/log/openhab2/events.log <==
2022-11-21 17:27:27.843 [vent.ItemStateChangedEvent] - BS_Office_Motion changed from ON to OFF
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:27:27.848 [INFO ] [smarthome.model.script.Motion Office] - No more movement -> Timer start.
==> /var/log/openhab2/events.log <==
2022-11-21 17:27:39.932 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 26 to 25
2022-11-21 17:27:50.019 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 25 to 26
2022-11-21 17:28:10.135 [vent.ItemStateChangedEvent] - BS_Office_Luminance changed from 26 to 25

2022-11-21 17:28:27.865 [ome.event.ItemCommandEvent] - Item 'gLightMainOffice' received command OFF
==> /var/log/openhab2/openhab.log <==
2022-11-21 17:28:27.864 [INFO ] [smarthome.model.script.Motion Office] - Timer elapsed -> Light OFF

What do you need a flag for?
You don’t need a timer unless you turn the light on.

when
   motion begins
then
  if (conditions are met) {
      turn light on
      if a timer is already started, cancel it, and make handle null
   } end of 'if()'
end of rule

when
   motion ends
then
  if (light is on) {
      make a new timer
      handle = start timer
             [ timer code for later:
               turn light off
               set handle null
             ] end of timer code
   } end of 'if()'
end of rule

I have change the code according to your proposal. It seems to work but I have one more challenge.
When the conditions are met and timer starts if someone in this state switches the rule button off (deactivates the rule) and leaves the room, lights will turn OFF when timer elapses.
My intention is to not react at all if the rule button will be deactivated and leave the light status as it is. Just stop the rule completely. Activate it again from the beginning when the rule button is activated again

var Timer motionDelayTimer = null
val int timeoutMinutes = 7
val int OfficeBrightness = 25

rule "Office Motion Sensor ON" 

when 
    Item BS_Office_Motion changed from OFF to ON 
then
    var Luminance = BS_Office_Luminance.state as Number

    if((Luminance < OfficeBrightness) && (OfficeMainLightRule.state == ON)) {
        logInfo("Motion Office", "Conditions met")
        
        if(motionDelayTimer === null || motionDelayTimer.hasTerminated) {
            motionDelayTimer = null
            gLightMainOffice.sendCommand(ON)
        }
     }
end

rule "Office Motion Sensor OFF"

when
    Item BS_Office_Motion changed from ON to OFF
then
    if ((BS_Office_Main_Light_1_Switch.state == ON) || (BS_Office_Main_Light_1_Switch.state == ON)) {
       motionDelayTimer = createTimer(now.plusMinutes(timeoutMinutes), [ |
       motionDelayTimer = null
       gLightMainOffice.sendCommand(OFF)
       ])
    }
end

Yes. After the rule creates the Timer, it has an independent existence. You could delete the rule, it wouldn’t affect the scheduled timer.

Then don’t use the “rule button” as a user control.

Create a dummy Item that you can control from the regulare UI. call it “Enble Light Auto” or something.
Modify the rule and timer code to check the state of that Item before taking actions.