[OH4] Hue Motion Sensor - Reschedule Timer - Can't seem to figure it out

Hi,
I have been using OH for a while (since OH 2.x) and up until now I have found all answers to my questions in this community without the need to register.
Until now…
I have installed a Philips Hue Motion Sensor which I want to use, to trigger my kitchen lights.
I could of course, just use the Hue app, but I want to use OH for (almost) everything.
What do I want to achieve ?
I want the motion sensor to trigger a rule (this is easy).
What should this rule do ?
The rule needs to check
a) if group gPresence is ON (easy)
b) if the illuminance (reported by the motion sensor) is below a certain value (kind of easy)

  • if a) and b) are true, I want to switch on my lights and leave them on for 120s.
    If within these 120s the motion sensor triggers again, I want the lights to stay on and the timer re-run.

This is my rule

var Timer KitchenTimer = null
var Number HUE_LIGHT_LVL_LIMIT = 18|" lx"


rule "HUE - KITCHEN: LIGHTS ON WHEN DETECTION IS REGISTERED"
when
        Item HUE_KITCHEN_MD received update ON
then
        var corLightLevel = (HUE_KITCHEN_MD_LIGHT_LVL.state as Number).floatValue - (HUE_LIGHT_LVL_LIMIT as Number).floatValue
        logInfo("hue.rules","MOTION DETECTED ; CORRECTED LIGHT LEVEL " +corLightLevel.toString+"lx")
        if (gPresence.state == ON) 
        {
                if (corLightLevel < 1 )
                {
                        if (KitchenTimer === null || KitchenTimer.hasTerminated())
                        {
                                gHUE_KITCHEN.sendCommand(ON)
                                logInfo("hue.rules","KITCHEN: MOTION DETECTED - LIGHTS ON")
                                KitchenTimer = createTimer(now.plusSeconds(120),        [|
                                        gHUE_KITCHEN.sendCommand(OFF)
                                        logInfo("hue.rules","SWITCHING KITCHEN LIGHTS OFF")
                                        //KitchenTimer = null
                                ])
                        }
                        else
                        {
                                logInfo("hue.rules","RESCHEDULING KITCHEN LIGHT TIMEOUT")
                                KitchenTimer.reschedule(now.plusSeconds(120))
                        }
                }
        }
end

The reason for the “corLightLevel” is: the kitchen lights themselves can illuminate the sensor too much, so the rule does not trigger (as OH4 assumes its too bright already).
If anyone can think of an easier/simpler/better way to solve this, please feel free to comment.

The rescheduling seems not to work.
I think has something to do with my timer, but I cannot for the life of me figure out what’s wrong.
If I

KitchenTimer = null

as last part of my timer, the else-branch of my IF-check will never be reached.
Can anybody help me / point me in the right direction, as it is annoying to be standing in the dark.
Thanks

Use blockly to generate the correct code and compare.

Within blockly there is a code view, so that you can copy & paste the auto generated code into your own script, if you prefer to script it by yourself

Have never used blockly.
Is that the only way to debug ?

Edit:
So I have tried blockly
image

Results in this Code

if (typeof this.timers === 'undefined') {
  this.timers = [];
}

var scriptExecution = Java.type('org.openhab.core.model.script.actions.ScriptExecution');

var zdt = Java.type('java.time.ZonedDateTime');


if (typeof this.timers['KitchenTimer'] === 'undefined' || this.timers['KitchenTimer'].hasTerminated()) {
  this.timers['KitchenTimer'] = scriptExecution.createTimer(zdt.now().plusMinutes(2), function () {
    events.sendCommand('gHUE_KITCHEN', 'ON');
    })
} else {
  this.timers['KitchenTimer'].reschedule(zdt.now().plusMinutes(2));
}
events.sendCommand('gHUE_KITCHEN', 'OFF');

the only difference I can find is
my code:

KitchenTimer === null

blockly

KitchenTimer === 'undefined'

This doesn’t really help me though.

I’ve got this rescheduling timer. No idea if this is good code but it works :wink:
Maybe you can change it to your needs:

var Timer nTimer = null

rule "switch_off_timer_wohnzimmer"
	when
		Item gMotionWohnbereich changed to ON or
		Item gMotionGang changed to ON
  	then		
		if (nTimer === null) {  
							
			nTimer = createTimer(now.plusSeconds(180), [ |
				
				if (gMotionWohnbereich.state == OFF && gMotionGang.state == OFF) {
					nTimer = null
					WallpanelWohnzimmerSetState.postUpdate(OFF)
					WallpanelGangSetState.postUpdate(OFF)
					LedClockSetState.postUpdate(OFF)
					AWTrixWohnzimmerSetState.postUpdate(OFF)
					logInfo("hue_motion.rules", "Wallpanel-Timer abgelaufen")
				}
				else {
					nTimer.reschedule(now.plusSeconds(180))
					logInfo("hue_motion.rules", " - Wallpanel-Timer abgelaufen aber noch Bewegung da - rescheduled")
				}
				])

			logInfo("hue_motion.rules", "Wallpanel-Timer gestartet")
			WallpanelWohnzimmerSetState.postUpdate(ON)
			WallpanelGangSetState.postUpdate(ON)
			LedClockSetState.postUpdate(ON)
			AWTrixWohnzimmerSetState.postUpdate(ON)
			}
			 
		else {
				nTimer.reschedule(now.plusSeconds(180))
				logInfo("hue_motion.rules", " - Wallpanel-Timer rescheduled")
			}
end

Cheers
Harry

This is a straight forward [Deprecated] Design Pattern: Motion Sensor Timer problem.

I see nothing wrong with the Timer code itself. There must be something wrong with the if/else logic. Make sure you log out the values of everything.

Well, you could use the Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9] to handle the timer and rescheduling the timer and then you’d just need to write a rule that checks the presence and light level and turn on/off the light as required.

The configuration would be something like this:

  • Write a rule. File based rules van work but a UI rule will work better. The following is a UI rule (let’s say the UID is “kitchenLight”). Notice there are no triggers, and there is only one line of code in the script Action and no code is written for the rule conditions, just configuration and that the condition for the light level uses units directly (I just compare to < 19 lx rather than subtracting 18 lx and comparing < 1 lx which is the same thing).
configuration: {}
triggers: []
conditions:
  - inputs: {}
    id: "1"
    configuration:
      itemName: gPresence
      state: ON
      operator: =
    type: core.ItemStateCondition
  - inputs: {}
    id: "2"
    configuration:
      itemName: HUE_KITCHEN_MD_LIGHT_LVL
      state: 19 lx
      operator: <
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: "items.gHUE_KITCHEN.sendCommand((isInitialAlert) ? 'ON' : 'OFF');"
    type: script.ScriptAction
  • put HUE_KITCHEN_MD Item into a Group and use that Group as “Triggering Group”
  • Threshold State: ON
  • Comparison operator: ==
  • Alert Delay: PT2M
  • Alert Rule: kitchenLight
  • Initial Alert Rule: kitchenLight
  • leave the rest of the properties set to the defaults, though some may be of interest like the DND period.

With a just a tiny bit of work you could use this one template rule with all you motion sensors. You’ll just need to use the alertItem’s name to get the name of the light to control in your rule.

If you coded this in the UI without the rule template, you can move the checks to the rule conditions (“but only if…”) so your script action would only need to handle the timer stuff. That would look something like this (note I’ve switched to Rules DSL for the script action below):

configuration: {}
triggers:
  - id: "3"
    configuration:
      itemName: HUE_KITCHEN_MD
    type: core.ItemCommandTrigger
conditions:
  - inputs: {}
    id: "1"
    configuration:
      itemName: gPresence
      state: ON
      operator: =
    type: core.ItemStateCondition
  - inputs: {}
    id: "2"
    configuration:
      itemName: HUE_KITCHEN_MD_LIGHT_LVL
      state: 19 lx
      operator: <
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "4"
    configuration:
      type: application/vnd.openhab.dsl.rule
      script: |-
        var timer = privateCache.get('timer');

        if(timer === null || timer.hasTerminated()){
          gHUE_KITCHEN.sendCommand(ON)
          privateCache.put('timer', createTimer(now.plusMinutes(2), [ |
                                      gHUE_KITCHEN.sendCommand(OFF)
                                      privateCache.put()
                                    ]))
        }
        else {
          timer.reschedule(now.plusMinute(2))
        }
    type: script.ScriptAction

Notice how much simpler the code becomes. Also see how we use the cache, which I strongly recommend over global variables whether you are working in UI rules (where there are no global variables) or files.

You can also simplify this code by failing fast.

rule "HUE - KITCHEN: LIGHTS ON WHEN DETECTION IS REGISTERED"
when
        Item HUE_KITCHEN_MD received update ON
then
    // Exit if no one is home or it's too bright already
    if(gPresence.state != ON || HUE_KITCHEN_MD_LIGHT_LVL.state as QuantityType< Illuminance> < 19|lx) {
        return;
    }

    var timer = privateCache.get('timer');
    if(timer === null || timer.hasTerminated) {
        gHUE_KITCHEN.sendCommand(ON)
        privateCache.put('timer', createTimer(now.plusMinutes(2), [ |
            gHUE_KITCHEN.sendCommand(OFF)
            privateCache.put('timer', null)
        ])
    }
    else {
        timer.reschedule(now.plusMinutes(2))
    }
end

By failing fast by returning up front the whole code becomes more compact and easy to read and therefore analyze and maintain. Note I use the privateCache here as well.

I just typed in the above. There may be errors and typos.

That won’t show how to do it in Rules DSL though. That shows JS Scripting.

1 Like

Thanks for your help.
I used your info and updated/cleaned my code (not really changing anything to my knowledge) and until now it seems to be working.
Even

KitchenTimer = null

seems to working.
I’ll keep an eye on it and come back if I run into further issues.

Ok,
so here I am again.
The most annoying “problem” is when it is getting dark (but not dark yet).
The illuminance is below my threshold (e.g. 19lx) and my motion sensor detects motion and triggers the kitchen lights to turn on.
Now the illuminance is above my threshold and the item’s state updates to the new value (i.e. above 19lx).
Now the lights turn off again, as the timer ends.
On the next motion detection, the lights are off, but the stored illumination value is still above my threshold ==> lights stay off.
The sensor seems not to update the illumination immediately.
How can I manually update the illumination reading from the sensor ?
It would be great if I could get the current reading and not the last reported one.

I hope I have described my “issue”.

Many bindings support a special command REFRESH which causes the binding to go out and query the device and update the Item.

The problems are

  1. not all bindings support this
  2. it’s asynchronous, your rule won’t wait around for the Item to update
  3. even if the binding supports it, there’s no guarantee that the refresh worked

In short, this approach is going to be challenging to implement and never work that well in the first place.

A better approach would be to use a different higher threshold for the light level when rescheduling the timer. So you would test for the higher threshold in the rule condition or to of the rule. Then test for the lower threshold before creating the timer.

That will only turn on the light when it’s below 19 lx but reschedule if it’s below 100 lx (or what ever value makes sense.

1 Like

My light sensor is also changing frequently.
I have created an additional item, with last 30 min average illuminance.

With this approach there are less fluctuations in the illuminance items state. I only use the last 30 min avg illuminance in my rules

Thanks for idea.
I’ve implemented a check and now I need to tweak the timeout and light-level-limit.
Seems to be working.

var HUE_LIGHT_LVL_LIMIT_HIGH = 1.5 * HUE_LIGHT_LVL_LIMIT_LOW
logInfo("hue.rules","MOTION DETECTED")
if (gPresence.state != ON || HUE_KITCHEN_MD_LIGHT_LVL.state > HUE_LIGHT_LVL_LIMIT_LOW || stat_QREVO_STATUS.state != "Charging")
{
	return;
}
logInfo("hue.rules","MOTION DETECTED ; LIGHT LEVEL " +String.format("%.2f",(HUE_KITCHEN_MD_LIGHT_LVL.state as Number).floatValue))
if (KitchenTimer === null || KitchenTimer.hasTerminated())
{
	gHUE_KITCHEN.sendCommand(ON)
	logInfo("hue.rules","KITCHEN: MOTION DETECTED - LIGHTS ON")
	KitchenTimer = createTimer(now.plusSeconds(HUE_MD_TIMEOUT),	[|
		gHUE_KITCHEN.sendCommand(OFF)
		logInfo("hue.rules","SWITCHING KITCHEN LIGHTS OFF")
		KitchenTimer = null
	])
}
else 
{
	//logInfo("hue.rules","KitchenTimer either running or != null")
	if (HUE_KITCHEN_MD_LIGHT_LVL.state < HUE_LIGHT_LVL_LIMIT_HIGH)
	{
		logInfo("hue.rules","RESCHEDULING KITCHEN LIGHT TIMEOUT ; LIGHT LEVEL "+String.format("%.2f",(HUE_KITCHEN_MD_LIGHT_LVL.state as Number).floatValue).toString+" < "+ HUE_LIGHT_LVL_LIMIT_HIGH)
		KitchenTimer.reschedule(now.plusSeconds(HUE_MD_TIMEOUT)) 
	}
}

Hi, I have a question.
Why are you doing with Timer?
My motion sensor (Shelly and Ikea) sends ‘ON’ when motion detected and sends ‘OFF’ if there is no movement for 1 minute.
The time is adjustable.
There is no need for timers…
If I’m dancing in front of the sensor for 10 minutes the light stays on…
Is hue different?
Greets

It seems as if the Hue sensor does work differently.
Within the hue app I have set the motion sensitivity to its highest setting.

2024-01-03 19:47:43.721 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HUE_KITCHEN_MD' changed from OFF to ON
2024-01-03 19:47:58.075 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HUE_KITCHEN_MD' changed from ON to OFF

As you can see, the hue sensor reports “ON” when movement is detected and reports “OFF” within 15s (and no movement).
The timeout is pretty short.

Edit:
You pushed me in the correct direction ( I think).
Turning on my lights when the motion sensor updates “ON” works like a charm.
I am now using a timer to delay the lights turning off.
I’ll see if that works better.

I have hue sensors and I use the UI and have a rule to turn the light on when the sensor is triggered.
The rule has but only if the lumination is less than 2.
The light I use has an expiration timer set for 2 minutes and that resets if the sensor sends again.
No code needed need. Works well.

I think I was really overthinking it.
My solution is putting an “expire” in my items definition

Group:Dimmer:AVG                gHUE_KITCHEN            "Kitchen Light(s) [%d %%]"      (gHUE_TOTAL,gKitchen)           ["Lightbulb","Light"]   { expire="30s,command=OFF" }
if (gPresence.state != ON || HUE_KITCHEN_MD_LIGHT_LVL.state > HUE_LIGHT_LVL_LIMIT_LOW || stat_QREVO_STATUS.state != "Charging")
        {
	return;
        }
gHUE_KITCHEN.sendCommand(ON)
logInfo("hue.rules","KITCHEN: MOTION DETECTED - LIGHTS ON; LIGHT LEVEL " +String.format("%.2f",(HUE_KITCHEN_MD_LIGHT_LVL.state as Number).floatValue))

This is my rule:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: Zigbee_Hue_Greg_motion
      state: OPEN
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    description: Only run the rule if the lux value is less than 3lx
    configuration:
      itemName: Zigbee_Hue_Greg_luminence
      state: "2"
      operator: <=
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "5"
    configuration:
      command: ON
      itemName: Lamp_Bed_Greg
    type: core.ItemCommandAction
  - inputs: {}
    id: "6"
    configuration:
      command: "50"
      itemName: Lamp_Bed_Gregwhite
    type: core.ItemCommandAction
  - inputs: {}
    id: "7"
    configuration:
      command: "50"
      itemName: Lamp_Bed_GregCT
    type: core.ItemCommandAction

It looks like this:
Screenshot from 2024-01-04 08-00-18

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.