Design Pattern: Motion Sensor Timer

Ah ok, makes sense.

Forgot to mention you’re also missing some closing brackets I think

timers.put(sw.name, createTimer(now.plusMinutes(timeoutMinutes), [|
                        sw.sendCommand(OFF)
                        timers.put(sw.name, null)
                ]))

Indeed. That error has been corrected as well.

I’m trying the whole day to get a motion sensor timer code running with my fibaro fgms 001 motion sensor and milight bulbs, but I guess I need some help…

This is what I have so far:

.items

	//EZ_Auge
	Number EZ_Lux "Motion Sensor [%.2f Lux]"    <sun>                     (ZwaveEye1) { channel="zwave:device:5b97d7f6:node4:sensor_luminance" }
	Number EZ_Battery "Motion Sensor [%d %%]"      <battery>    (ZwaveEye1)        { channel="zwave:device:5b97d7f6:node4:battery-level" }
	Number EZ_Temp "Motion Sensor [%.1f C]"     <temperature>    (ZwaveEye1)    { channel="zwave:device:5b97d7f6:node4:sensor_temperature" }
	Switch EZ_Motion "Motion Sensor Motion [%s]"           (ZwaveEye1)     { channel="zwave:device:5b97d7f6:node4:sensor_binary" }
	Contact EZ_MotionC "Motion Sensor MotionC [%s]"           (ZwaveEye1)     { channel="zwave:device:5b97d7f6:node4:sensor_binary" }
	Switch EZ_Alarm "Motion Sensor Alarm [%s]"             <fire>   (ZwaveEye1)      { channel="zwave:device:5b97d7f6:node4:alarm_general" }
	Switch EZ_Burglar "Motion Sensor Alarm b [%s]"             <fire>   (ZwaveEye1)      { channel="zwave:device:5b97d7f6:node4:alarm_burglar" }
	Number EZ_Seismic "Motion Sensor Seismic [%f]"                (ZwaveEye1)      { channel="zwave:device:5b97d7f6:node4:sensor_seismicintensity" }

.rules

	//EZ Auge
    var Number counter = 0
    var Number lastCheck = 0

    rule "EZ Auge - Licht An"
    when   
            Item EZ_Motion changed from ON to OFF
    then   
            counter = counter + 1
            //if(EZ_Lux.state < 40) {
            sendCommand(Scene_EZ, 3)
            }
    end

    rule "EZ Auge - Licht Aus"
    when   
            Time cron "0 * * * * ?"
    then   
            if(lastCheck == counter) {
                    counter = 0
                    lastCheck = -1;
                    sendCommand(Scene_EZ, 1)
                    sendCommand(EZ_Motion, OFF)
            } else {
                    lastCheck = counter
            }
    end

	// Scenes EZ
	rule "Scene EZ"
	when
	Item Scene_EZ received command 
	then
	/*Aus*/ if (receivedCommand==1) { 
	/*Aus*/ sendCommand(Scene_ColorSelect_EZ_Deckenlampe, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)))
	/*Aus*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Buecherregal, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)))
	/*Aus*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Sideboard, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)))
	}
	/*Normal*/ if (receivedCommand==2) { 
	/*Weiss*/ sendCommand(Scene_ColorSelect_EZ_Deckenlampe, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(70)))
	/*Aus*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Buecherregal, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)))
	/*Aus*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Sideboard, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(0)))
	}
	/*Hell*/ if (receivedCommand==3) { 
	/*Weiss*/ sendCommand(Scene_ColorSelect_EZ_Deckenlampe, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(100)))
	/*Weiss*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Buecherregal, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(100)))
	/*Weiss*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Sideboard, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(100)))
	}
	/*Stimmung*/ if (receivedCommand==4) { 
	/*Lila*/ sendCommand(Scene_ColorSelect_EZ_Deckenlampe, new HSBType(new DecimalType(300),new PercentType(100),new PercentType(100)))
	/*Grün*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Buecherregal, new HSBType(new DecimalType(130),new PercentType(100),new PercentType(100)))
	/*Türkis*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Sideboard, new HSBType(new DecimalType(190),new PercentType(100),new PercentType(100)))
	}
	/*Grün*/ if (receivedCommand==5) { 
	/*Grün*/ sendCommand(Scene_ColorSelect_EZ_Deckenlampe, new HSBType(new DecimalType(130),new PercentType(100),new PercentType(100)))
	/*Grün*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Buecherregal, new HSBType(new DecimalType(130),new PercentType(100),new PercentType(100)))
	/*Grün*/ Thread::sleep(500) sendCommand(Scene_ColorSelect_EZ_Sideboard, new HSBType(new DecimalType(130),new PercentType(100),new PercentType(100)))
	}
	end

The scene rules are working manually with some buttons.

This is probably better posted as a new topic as your implementation has almost nothing to do with the Design Pattern described above.

“EZ Auge - Licht An” has a syntax error. With the if statement commented out the } is incorrect and an error like this could prevent the rest of the rules in this file from being loaded.

Avoid using C style comments (i.e. /*) . I’ve seen some reports of people having problems when using this type of comments. That probably isn’t the case here since you said they work.

The following is what your code does as written. I’m writing this out because based on this code I have no idea what you are trying to do.

  • When EZ_Motion turns OFF one up a counter and send 3 to Scene_EZ.

  • Every second, if lastcheck == counter reset counter and lastCheck, send 1 to Scene_EZ and turn off EZ_Motion (which triggers the EZ Auge - Licht An rule.

  • When Scene_EZ receives a command 3 it sets the color of a few lights with half second sleeps between (it is a lot easier to read if you put each line on its own line).

  • When Scene_EZ receives a command 1 it turns off these lights.

Given that the flow would go something like the following.

  1. EZ Auge - Licht Aus triggers. lastCheck == counter (both start off at 0) so lastcheck is set to -1, Scene_EZ is sent 1, and EZ_Motion is set to OFF.

  2. Scene_EZ 1 runs turning off the lamps.

  3. If EZ_Motion is OFF nothing else happens. If EZ_Motion is ON counter becomes 1 and Scene_EZ 3 runs turning on the lamps.

  4. If EZ_Motion was OFF, when EZ Auge - LichtIf runs again lastCheck != counter so lastCheck gets set to 0. EZ_Motion was ON, one second later EZ Auge - Licht Aus runs again. this time lastCheck != counter so lastCheck gets set to 1 (the current value of counter).

  5. EZ_Motion turns ON and OFF again based on motion. At that point counter is set to 1 or 2 and the lamps are turned on again. Then step 1 runs again.

Honestly this code is unclear and overly complicated and exceptionally difficult to follow, even if it did work. Assuming what you are trying to do is keep a light on for a certain period of time after the last time a motion sensor went off, is there some reason you don’t want to use a Timer like the example at the top of this thread?

Sorry for that, what I trying to do is combining your timer code with the wiki-example I already posted and which can be found here: How to turn on light when motion detected and is dark?

OK thanks, I will avoid this.

I try a, I think very basic thing at home automation: “indoor lights-on at motion detection when it’s dark and lights-off after a given time” so I don’t have to switch on and off lights anymore and won’t forget lights on.

It is a very basic thing but this approach with counters is not a very easy way to do it.

Using the example at the top of the thread…

var Timer ezMotionTimer = null

rule "EZ Auge - Licht Aus"
when
    Item EZ_Motion received command
then
    Scene_EZ.sendCommand(3)

    if(ezMotionTimer != null) {
        ezMotionTimer.reschedule
    }
    else {
        ezMotionTimer = createTimer(now.plusMinutes(5), [|
            Scene_EZ.sendCommand(1)
            ezMotionTimer = null
        }
    }
end

The above will turn on the Lights (i.e. sendCommand 3 to EZ_Scene). Five minutes after the last time the EZ_Motion receives a command the Timer will go off and turn off the lights.

I’ve a few other minor comments about your rule. These are not problems but could help you avoid problems in the future.

It is better to use the sendCommand and postUpdate method on the Item rather than the actions. So you would rewrite:

sendCommand(Scene_ColorSelect_EZ_Deckenlamp, new HSBType(new DecimalType(0),new PercentType(0),new PercentType(70)))

as

Scene_ColorSelect_EZ_Deckenlamp.sendCommand(new HSBType(new DecimalType(0),new PercentType(0),new PercentType(70)))

ColorItems can receive ONOFFType and PercentType commands. So where you set the bulb to HSBType(0,0,0) you can just sendCommand(OFF).

1 Like

Thanks for the hints, I changed my code as you recommend.

But the timer code is still not working.

This is working -> items and sensor are OK

	rule "WZ Auge - Licht"
	when
    Item WZ_Burglar received update
	then
    Scene_WZ.sendCommand(2)
    end

But this does nothing

	var Timer ezMotionTimer = null

	rule "WZ Auge - Licht Timer"
	when
    Item WZ_Burglar received update
	then
    Scene_WZ.sendCommand(3)

    if(ezMotionTimer != null) {
        ezMotionTimer.reschedule
    }
    else {
    	ezMotionTimer = createTimer(now.plusMinutes(1))
    	Scene_WZ.sendCommand(1)
    	ezMotionTimer = null
    	}
    }
	end	
    else {
    	ezMotionTimer = createTimer(now.plusMinutes(1))
    	Scene_WZ.sendCommand(1)
    	ezMotionTimer = null
    	}
    }

Needs to be

    else {
    	ezMotionTimer = createTimer(now.plusMinutes(1)) [ |
    	    Scene_WZ.sendCommand(1)
    	    ezMotionTimer = null
    	]
    }

Thanks very much, but still no reaction

    var Timer ezMotionTimer = null

	rule "Licht Timer"
	when
    Item EZ_Auge_Burglar changed from OFF to ON
	then
    Scene_EZ.sendCommand(3)

    if(ezMotionTimer != null) {
        ezMotionTimer.reschedule
    }
    else {
    	ezMotionTimer = createTimer(now.plusMinutes(1)) [ |
    	    Scene_EZ.sendCommand(1)
    	    ezMotionTimer = null
    	]
    }
	end 

May I have to import some bibs or place the var at the top of the .rules or need some addons or extra services or so?

ezMotionTimer needs to be declared above any rule in that file.

Add logging statements so you know whether the rule is triggering.

That was the problem, now it works great.
Thank you very much :slight_smile:

In this topic I’ll go forward with some more terms: Motion detection → lights on - if it’s dark and within time period, with timer - Help needed

I don´t want to make the variables as global variables.

Can i make this inside the rule as well?

If i put val and var to the top of my rules-file, it works. But if i put it inside the rule, nothing happens…

working:

var Timer md1Timer = null
val int timeoutMinutes = 5

// KG Garage Licht aus nach 5 Minuten

rule "KG Garage Licht auto off"
when
    Item Licht_KG_Garage_1 changed
then
	if (Licht_KG_Garage_1.state == ON) {
			md1Timer = createTimer(now.plusMinutes(timeoutMinutes), [|
				Licht_KG_Garage_1.sendCommand(OFF)
				md1Timer = null
			]
	}
    else {
			md1Timer = null
    }
end

not working:

// KG Garage Licht aus nach 5 Minuten

rule "KG Garage Licht auto off"
when
    Item Licht_KG_Garage_1 changed
then
        var Timer md1Timer = null
        val int timeoutMinutes = 5
	if (Licht_KG_Garage_1.state == ON) {
			md1Timer = createTimer(now.plusMinutes(timeoutMinutes), [|
				Licht_KG_Garage_1.sendCommand(OFF)
				md1Timer = null
			]
	}
    else {
			md1Timer = null
    }
end

By doing this you are defining local vs. global variables. Local in this case means (as in all programming languages) the scope and the existence of this variable is limited to one execution of the code block, being your rule. The Timer variable will be deleted when reaching end and recreated when executing the rule again.

Yes, i know this. I want to have this variable “var Timer md1Timer = null” only inside my rule. The rule will not reach “end” while the timer is running.

And when the timer is “null”, i don´t need it anymore.

But this doesn´t work with “var timer…” inside the rule. No error-message in the log-file. I made some logging and the rule will not get triggered, if i put var inside it.

Do you know the reasen, why it doenst´t work?

I don’t think you understand how Timers work. They do not block the rest of the execution of your Rule. Once created the timer runs in the background while the rest of your Rule continues to execute. So in the above your rule immediately exits once the Timer is created instead of waiting around for the timeout.

This is why you must store the timer variable as a global.

See the following:

Your rule above, “KG Garage Licht auto off”, could be removed and replaced with adding the expire binding to the item:

Switch Licht_KG_Garage_1 "KG Garage Licht" { yourbinding="..." or channel="...", expire="5m,command=OFF" }

I found this very useful, it allows you to have different timeout values for items as well which is a benefit.

HI @rlkoshak, I try to run your code but with error, I don’t know how to make it work,
do you mind gave a hand on it?

10:24:45.871 [ERROR] [.script.engine.ScriptExecutionThread] - Rule ‘A Motion Detector triggered’: org.eclipse.smarthome.core.library.items.SwitchItem

import java.util.Map

val Map<String, Timer> timers = newHashMap

rule "A Motion Detector triggered"
when
    Item MS_LivingRoom_MotionSensor1 received update MOTION or
    Item MS_LivingRoom_MotionSensor2 received update MOTION
then
    Thread::sleep(100) // give persistence time to save the update
    val sw =  gMotionSensor.members.sortBy[lastUpdate].last as SwitchItem
    logInfo("Motion.Sensor", "Motion Sensor Counter start")

    val timeoutMinutes = 3 // use an appropriate value

    if(timers.get(sw.name) == null){
        timers.put(sw.name, createTimer(now.plusMinutes(timeoutMinutes), [|
        	logInfo("Motion.Sensor", "Motion Sensor Counter works")
            sw.sendCommand(OFF)
            timers.put(sw.name, null)
        ]))
    }
    else {
        timers.get(sw.name).reschedule(now.plusMinutes(timeoutMinutes))
        logInfo("Motion.Sensor", "Motion Sensor Counter timer extend")
    }
    logInfo("Motion.Sensor", "Motion Sensor Counter finished")
end

What item types are you motion detectors. I’ve never seen an Item with a MOTION state. In the rule you cast then to a SwitchItem which only has the states of ON and OFF.

Do you get the first log statement in your logs?

You may need to filter out the items that have yet to be saved to persistence.

val sw =  gMotionSensor.members.filter.[s.lastUpdate != null].sortBy[lastUpdate].last as SwitchItem

I use mihome, which motion sensor announce MOTION when detect motion,
I set String type at items,

yesterday I have setup mapdb and have * : strategy = everyChange, restoreOnStartup in mapdb.persist
I remove restoreOnStartup from influx and I see item state restored after system startup,
so I assume value should have saved.

I really don’t know how to do coding, according to your last reply, does your code only able to apply to item type is switch?