Design Pattern: Motion Sensor Timer

motionsensor
motiondetection
designpattern
Tags: #<Tag:0x00007f183236afa0> #<Tag:0x00007f183236a500> #<Tag:0x00007f183236a028>

(Rich Koshak) #1

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for details on how to use DPs.

Problem Statement

Many motion detectors only report ON when they detect motion but do not send a corresponding OFF after no motion has been detected after a period of time. In addition, often one wants to control a device like a light for a certain period after the last motion is seen, not after the motion sensor goes to OFF. Therefore one needs to keep an occupancy switch ON (or Contact OPEN) until the desired amount of time after the last motion is detected.

This approach works any time one wants something to happen a certain amount of time after the last time that an Item received a command or update. It can be easily adapted to report when a device stops reporting (see Generic Is Alive), to avoid flapping (e.g the switch constantly switching back and forth when somthing is on the edge of detection, see Presence Detection Rule), implementing the Latch Pattern, etc.

Concept

When the motion detector sends an ON command, create a timer. If subsequent ON commands are received reschedule the timer. When the Timer expires, turn the Occupancy Switch OFF.

Simple Example: Expire Binding Version

Items

Switch Occupancy1 { expire="5m,command=OFF" }
Switch MotionDetector1 { expire="5m,command=OFF" }

Rules

rule "Motion sensor triggered"
when
    Item MotionDetector1 received update ON
then
    Occupancy1.sendCommand(ON)
end

Theory of Operation

That is all there is to it. When the MotionDetector1 receives and ON update it sends an ON command to the occupancy switch. The Expire binding will set the occupancy Switch to OFF five minutes after the last ON command is received.

Simple Example: Timers Version

Items

Switch MotionDetector1

Rules

var Timer occupancyTimer = null
val int timeoutMinutes = 5 // choose an appropriate value

rule "MotionDetector1 received ON"
when
    Item MotionDetector1 received update ON
then
    if(occupancyTimer === null) {
        occupancyTimer = createTimer(now.plusMinutes(timeoutMinutes ), [|
            MotionDetector1.sendCommand(OFF)
            occupancyTimer = null
        ])
    }
    else {
        occupancyTimer.reschedule(now.plusMinutes(timeoutMinutes )
    }
end

Theory of Operation

When MotionDetector1 receives and ON update create a Timer to turn it OFF in 5 minutes. If there already is a timer, reschedule the Timer.

Comprehensive Example

This example shows one way one can manage and consolidate the above logic for multiple Motion Detectors. The following rule depends upon persistence having been set up on the MotionDetector Items and assumes that detectors will not trigger really really close together.

Items

Group gMotionDetectors
Switch MotionDetector1 (gMotionDetectors)
Switch MotionDetector2 (gMotionDetectors)

Rules

import java.util.Map

val Map<String, Timer> timers = newHashMap

rule "A Motion Detector triggered"
when
    Member of gMotionDetectors received command ON
then

    val timeoutMinutes = 5 // use an appropriate value

    if(timers.get(triggeringItem.name) === null){
        timers.put(triggeringItem.name, createTimer(now.plusMinutes(timeOutMinutes), [|
            triggeringItem.sendCommand(OFF)
            timers.put(triggeringItem.name, null)
        ]))
    }
    else {
        timers.get(triggeringItem.name).reschedule(now.plusMinutes(timeOutMinutes))
    }
end

Theory of Operation

We store the Timers in a Map using the name of the Motion Detector Item as the key. When one of the members of gMotionDetectors receive an ON command create a Timer for that detector to go off in 5 minutes. If one already exists reschedule it. Store the Timer in the Map.

Here is the version of the above code using the Expire binding:

Switch MotionDetector1 { expire="5m,command=OFF" }
Switch MotionDetector2 { expire="5m,command=OFF" }

Look ma, no code! The above does exactly the same thing as the complex example above with the Map and Timers.

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Sensor Aggregation The complex example of Sensor Aggregation uses this DP to implement an ant-flapping timer.

Motion detection -> lights on - if it's dark and within time period, with timer - Help needed
Design Pattern: Sensor Aggregation
Rule to trigger presence with motion detectors
Rule check for Motion Detection
OpenHAB 2.0 Rules: Create list of HSBTypes
Timer Rule Problem
Devolo zwave motion sensor mt02647 binary sensor always triggered
Motion detection getting me crazy
Change item state with rules, help
Keep off the actions spam
Presence detection via iPhone's mDNS Entry. How to integrate into OH2?
My openHab Setup
Problem with rule
Rules and special variable forms
PIR sensor, timer rule won't work as intended
Making Decisions Based on Time of Day
Switch set on time
PIR + LED ligth
Design Pattern: Expire Binding Based Timers
Simple Motion Rule Question
Openhab2 rules not working for me
Functions in rules
Rule that send command off to item only if item is off since 10 min
Example of a Lambda Function for timing the turning on and off of Lights (or switches)
Openhab 2 rules not catching changes to strings
Help, I'm ready to give up
Start one binding with another binding
Set last tripped/alarm date, or last action date to an item - best practice?
Fibaro fgms-001 association groups
Question about how createTimer works
Problems with OneWire hang ups
Help with Recommendation
Help with Recommendation
Xiaomi Motion sensor with Milights use in rules
Remembering which lights were turned off
[SOLVED] Rule not working properly - Lights out after Timer
[SOLVED] Simple Switch OFF rule
[SOLVED] MQTT populating temperature values - no it is not!
Design Pattern: Associated Items
[SOLVED] Trying to avoid "debounce" in a rule
[SOLVED] Help with Motion sensor goes too off too quick / canceling a timer
Please test the new Expire Binding
Rules start only after rewrite the rule file
Using a previousState for timer rule
(Rossko57) #2

Question -
Can we use instead

when
   Item gMotionDetectors received update ON

for a more generic rule?
(note, I believe when triggering from a Group we should look for ‘update’ rather than 'command’
Furthermore, we should bear in mind a group may receive multiple updates for a single “real event”. But in this example, I think repeated execution will have no harmful effect.)


(Rich Koshak) #3

I haven’t tested with it that way yet. My first inclination was to write the rule that way. However, I don’t know what state comes as the update. My suspicion is that the Group’s state comes as the update. So, if for example, you have Group:AND(ON, OFF), which is perfectly reasonable, the updates will be all OFF unless all the switches are ON. And if you leave the Group with default or use OR all the updates will be ON as long as at least one Switch is ON.

Consequently there will be no way to distinguish between ON and OFF updates for a given Switch.

I’m pretty sure it could be made to work but I chose to implement it this slightly less generic way to be less error prone for inexperienced users who may try to use it.

If you get it to work triggering on the Group updates (you will have to trigger on all updates, not just ON) post it here. I will do the same if I ever get to it.

I primarily wrote this as an answer to a question so didn’t spend a whole lot of time on it.


(Tom) #4

FYI

plusMinutes(timeOutMinutes)

Should be

plusMinutes(timeoutMinutes)

Weirdly I also found the import line is required for it to work in Openhab2 else timers.get throws an exception.

import java.util.Map

(Rich Koshak) #5

Corrected the typo.

That is correct, OH 2 automatically imports all the core openHAB and ESH stuff but not other utilities like java.util.*, etc. The import is there in the second example above.


(Tom) #6

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)
                ]))

(Rich Koshak) #7

Indeed. That error has been corrected as well.


(Stefanseiner) #8

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.


(Rich Koshak) #9

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?


(Stefanseiner) #10

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.


(Rich Koshak) #11

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).


Milight Binding + Scene rules = sometimes no reaction
(Stefanseiner) #12

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	

(Rich Koshak) #13
    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
    	]
    }

(Stefanseiner) #14

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?


(Rich Koshak) #15

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

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


(Stefanseiner) #16

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


(Hallo Ween) #17

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

( ) #18

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.


(Hallo Ween) #19

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?


(Rich Koshak) #20

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: