[Deprecated] Design Pattern: Motion Sensor Timer

Hi Chris,

thanks for your rule example.

On my try i got:
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘EGFlurMotionOFF’: The name ‘EGFlurVDMotionOverride’ cannot be resolved to an item or type; line 12, column 10, length 22

Is xVDMotionOverride to set as an item?

GoodnightMode and CozyMode too?

Hi Marcello

I have all my items stated in the example above as an item.
This way you can link them to your sitemap page and turn the motion detection on or off.

As of now the GoodnightMode and CozyMode are separate switches but would probably be better to put as a Number item and then just define them in a MAP file.

Chris

Hi Oliver,

I have currently defined the items CozyMode and GoodnightMode as a switch item and trigger GoodnightMode with a switch by the masterbedroom. CozyMode is currently set to trigger at 9PM with a cron rule if AwayMode (a switch by the frontdoor) is not activated.

The sonos are implemented for both bathrooms and the kitchen. The bathrooms detect motion, turns off the fan, dim the light up and turn on the selected radio channel and volume defined in my sitemap.

In the kitchen i sense the watt usage on the cooking top and turn on the Radio and fan. Fan turns off if the wattage is below a certain value for more than 30seconds. Wattage is definded in my sitemap.

I’ll try to upload some of my codes this weekend.

Regards

Chris

Or a String Item and skip the MAP and just make the String Item use the values you would have put into the MAP in the first place.

Here is what i use for Sonos, I use the same code for all Sonos and change the radio channel/channel string on top.

   val String Channel0 = "P4 Radio Norge"
    val String Channel1 = "Korean Pop"	
    val String Channel2 = "Liberdade"
    val String Channel3 = "Jazz"
    val String Channel4 = "P5"
    val String Channel5 = "Radio Sørlandet"
    val String Channel6 = "Nrk MP3"
    val String Channel7 = "BuddaBar"
    val String Channel8 = "Something "
    val String Channel9 = "Something else"

    rule "Sonos Bad1"
when
	Item Bad1VDSonos received update ON
	then if ((Bad1VDSonosOverride.state==OFF)&&(Bad1_Sonos_State.state!="PLAYING")){
		
		val volumebad1 = (Bad1VDSonosVolume.state as Number).intValue
		
		sendCommand(Bad1_Sonos_Volume,volumebad1){
		Thread::sleep(100)
		if 
		(Bad1SonosSelector.state==0){
		sendCommand(Bad1_Sonos_Favorite, Channel0)}
		else if 
		(Bad1SonosSelector.state==1){
		sendCommand(Bad1_Sonos_Favorite, Channel1)}
		else if
		(Bad1SonosSelector.state==2){
		sendCommand(Bad1_Sonos_PlayPlaylist, Channel2)} 
		else if
		(Bad1SonosSelector.state==3){
		sendCommand(Bad1_Sonos_PlayPlaylist, Channel3)}
		else if
		(Bad1SonosSelector.state==4){
		sendCommand(Bad1_Sonos_Favorite, Channel4)} 
		else if
		(Bad1SonosSelector.state==5){
		sendCommand(Bad1_Sonos_Favorite, Channel5)} 
		else if
		(Bad1SonosSelector.state==6){
		sendCommand(Bad1_Sonos_Favorite, Channel6)} 
		else if
		(Bad1SonosSelector.state==7){
		sendCommand(Bad1_Sonos_Favorite, Channel7)} 
		else if
		(Bad1SonosSelector.state==8){
		sendCommand(Bad1_Sonos_Favorite, Channel8)}
		else if
		(Bad1SonosSelector.state==9){
		sendCommand(Bad1_Sonos_Favorite, Channel9)}}}
		end
	
		

rule"Sonos OFF"
when
	Item Bad1VDSonos received update OFF
	then if (Bad1_Sonos_State.state!="STOPPED"){
	sendCommand(Bad1_Sonos_Stop,ON)} end

Some ideas:

If you use a lambda you can avoid duplicating the same code for each Sonos (if I understand your comment correctly).

If you use a Map you can drastically reduce the code in your rule. You can populate it using a HashMap but I prefer to use the MAP transform:

sonos.map

0=P4 Radio Norge
1=Korean Pop
2=Liberdade
3=Jazz
4=P5
5=Radio Sørlandet
6=Nrk MP3
7=BuddaBar
8=Something
9=Something else
rule "Sonos Bad1"
when
    Item Bad1VDSonos received update ON
then
    if(Bad1VDSonosOverride.state == OFF && Bad1_Sonos_State.state != "PLAYING"){
        Bad1_Sonos_Volume.sendCommand(Bad1VDSonosVolume.state as Number)
        Thread::sleep(100)
        Bad1_Sonos_PlayPlaylist.sendCommand(transform("MAP", sonos.map, Bad1SonosSelector.state.toString))
    }
end

rule"Sonos OFF"
when
    Item Bad1VDSonos received update OFF
then 
    if (Bad1_Sonos_State.state!="STOPPED"){
        Bad1_Sonos_Stop.sendCommand(ON)
    } 
end

With this approach not only is your Rule shorter, you can add and remove channels by simply editing the map file and you don’t have to touch your Rules. Note, it is better to call the method on the Item than to call the sendCommand Actions.

1 Like

Wow Rikoshak, this was a really good code upgrade, thank you very much :smile:

1 Like

One last little bit that can further simplify the rule is if you make Bad1SonosSelector become a String Item and have it populated with the values in sonos.map instead of a number, you can get rid of the MAP and just:

Bad1_Sonos_PlayPlaylist.sendCommand(Bad1SonosSelector.state.toString)

That is what I meant by “Or a String Item and skip the MAP and just make the String Item use the values you would have put into the MAP in the first place” in my posting above.

Thank for your help. Now it works.Here is my setting.
My Patio light is triggered on by a Patio motion sensor. The motion sensor turns itself off automatically after 3 Min. I want to keep the light on for 10 Min if no new motion detected.

Switch Patio_Motion             "Patio Motion Sensor[%s]" <motion> { channel="zwave:device:12345678:node1:sensor_binary,respond_to_basic=true" }
Switch Patio_Light              "Patio Light[%s]" <light> { channel="zwave:device:12345678:node2:switch_binary" }
var Timer Patio_Motion_Timer = null
val int timeoutMinutes = 10
rule "Patio_Motion_Light OFF by Timer"
when
    Item Patio_Motion changed from ON to OFF
then
    if(Patio_Motion_Timer == null)
     {
     Patio_Motion_Timer = createTimer(now.plusMinutes(timeoutMinutes)) [|
     Patio_Light.sendCommand(OFF)
     Patio_Motion_Timer = null]
     }
    else 
    {
    Patio_Motion_Timer.reschedule(now.plusMinutes(timeoutMinutes))
    }
end
1 Like
var Timer Patio_Motion_Timer = null

I think there is a problem with this rule. If MotionDetector1 changed first from OFF to ON a timer will be started. If then MotionDetector2 will triggered (a new timer will be started) and MotionDetector1 will now changed from ON to OFF and the timer will count down and after then the Light will go off - but the timer for MotionDetector2 is already counting … - so the rule should only work if the first triggered motion sensor will triggered again to reschedule the timer …

Is that right? And how could I avoid that?

And another question: Why send after counting down the timer the timer to OFF?

sw.sendCommand(OFF)

For one the code above doesn’t actually do anything except control the motion detectors. There is no code there to actually turn on or off any lights. The DP assumes you will have a rule (not shown) that triggers when MotionDetector1 received command OFF that implements the turning ON or OFF the light. Put another way, this DP covers the automatic turning off the MotionDetector Item, what you do with those events is an exercise left to the student.

If you are trying to control the same light using multiple motion detectors indeed, you would need to add some logic to the code that turns on/off the light (i.e. the code that is out of scope of this article) to check if there is a Timer running for the other MotionDetectors before actually turning off the light.

Does someone also have an idea to implement a dynamic expire interval?

Follow the first example in the OP only instead of using timeoutMinutes use a dynamically calculated number.

You cannot use a dynamic timeout with the Expire binding.

1 Like

So I am trying to get a motion sensor rule running what activates a siren (just a switch) when the detector has changed from OFF to ON, 3 times in a minute. This should avoid not necessary activations.
Can you help me here?
Please keep it simple because when I am reading your examples I am quite often lost - maybe your coding is too good for me :slight_smile:

The rule I am running (without this logic) so far is:

var Timer shutoffTimer = null

rule “SonoffMotion changed from OFF to ON 3 times in a minute”

when
	Item Sonoff1 changed from OFF to ON or  //Question: how to say 3 activations in a minute?
	Item Sonoff3 changed from OFF to ON		//Question: how to say 3 activations in a minute?
then
	if (shutoffTimer === null && Alarm.state == ON && lastRun.isBefore(now.minusMinutes(5))) {
		sendMail("xxx@gmail.com", "Sonoff1", "Sonoff1 motion")
		Sonoff2.sendCommand(ON)
		lastRun = now
		logInfo("RuleInfo", "Siren START")
		shutoffTimer = createTimer(now.plusSeconds(240)) [|
		Sonoff2.sendCommand(OFF)
		logInfo("RuleInfo", "Siren STOP")
		shutoffTimer = null
		]
	}

end

Unfortunately there is no simple solution for this. You need to have a counter, a timer, and a way to have the count gradually reduce over time. If you want simple you need to change your requirements.

I’m on my phone so can’t really code up something as complicated as this. But what ever it is will be complicated.

You need to create a collection to store timers. Every time the motion sensor goes to on look to see if there are two timers in the collection. If so then you know that the sensors have gone off three times in five minutes. If not create a new timer and add it to the list.

The body of the timer is to remove the timer from the list.

But the timer is already there. Why an additional or different timer?
I thought when one detector was triggering the alarm nothing should stop that timer and the siren now running for 240 seconds.
After the if timer is done the story can start again from any other motion detector. For the moment I just added two md here.

It just needs to count somehow 3 detections within a minute, and I have no idea how to reach it, can this be a variable?

It sounds so simple but how do you:

  • deal with the events that took place 61+ seconds ago? How do those time out? You can’t just keep a running count because the events have to time out over time

  • trigger the alarm immediately after the three events occur rather than having to wait up to a minute after the three events occur before triggering the alarm?

You need a timer for each ON event, not a Timer for when to turn off the siren.

Now that I’m at a computer, here is the code. It is as simple as it can be made.

import java.util.List

var List<Timer> timers = createArrayList
var Timer shutoffTimer = null
var lastRun = now

rule "SonoffMotion changed from OFF to ON 3 times in a minute"
when
    Item Sonoff1 changed from OFF to ON or
    Item Sonoff3 changed from OFF to ON
then
    // ignore the event if we are playing the siren already or the Alarm is OFF or it has been less 
    // then five minutes since the last time the siren was played
    if(shutoffTimer !== null || Alarm.state == OFF || lastRun.isAfter(now.minusMinutes(5))) return;

    // if timers.size < 2 we have not yet had three ON events in a minute
    if(timers.size < 2) {
        val t = createTimer(now.plusMinutes(1), [ | timers.remove(t) ])
        timers.add(t)
    }

    // if timers.size == 2 then this event is the thrid ON event in a minute
    if(timers.size == 2) {
        sendMail("xxx@gmail.com", "Sonoff1", "Sonoff1 motion")
        Sonoff2.sendCommand(ON)
        lastRun = now
        logInfo("RuleInfo", "Siren START")
        shutoffTimer = createTimer(now.plusSeconds(240)) [|
            Sonoff2.sendCommand(OFF)
            logInfo("RuleInfo", "Siren STOP")
            shutoffTimer = null
        ]
    }
end

It is the exact same concept but slightly “cleaner” to use Design Pattern: Expire Binding Based Timers.

Group:Number:SUM MotionEventTimers
Switch MotionEvent1Timer (MotionEventTimers) { expire="1m,command=OFF" }
Switch MotionEvent2Timer (MotionEventTimers) { expire="1m,command=OFF" }

// Add the expire to the Siren
Switch Sonoff2 ... { mqtt=... , expire="4m,command=OFF" }
var lastRun = now

rule "SonoffMotion changed from OFF to ON 3 times in a minute"
when
    Item Sonoff1 changed from OFF to ON or
    Item Sonoff3 changed from OFF to ON
then
    // ignore the event if we are playing the siren already or the Alarm is OFF or it has been less 
    // then five minutes since the last time the siren was played
    if(shutoffTimer !== null || Alarm.state == OFF || lastRun.isAfter(now.minusMinutes(5))) return;

    // if there are less than 2 ON timers, find the one that is OFF and set it to ON
    if(MotionEventTimers.state < 2) {
        MotionEventTimers.members.filter[ t | t.state == OFF ].head.sendCommand(ON)
    }

    // if timers.size == 2 then this event is the thrid ON event in a minute
    if(MotionEventTimers.state == 2) {
        sendMail("xxx@gmail.com", "Sonoff1", "Sonoff1 motion")
        Sonoff2.sendCommand(ON)
        lastRun = now
        logInfo("RuleInfo", "Siren START")
    }

    // The Expire binding will automatically turn off the siren
end

One could try to use persitence for this but because both ON and OFF events gets stored in the database I don’t think it will work. Persistence also doesn’t record NULL so we can’t know if the event was changing from OFF to ON.

I’m also using the expire binding on my Aeotec MultiSensor 6, but I have the issue where it correctly turns on the light, and then the logs seem to show the sensor doesn’t detect motion after it ‘trips’, so the light always turns off after 5 minutes.

Any suggestions on eliminating the light turning off when I’m still in the kitchen?

tail

2018-05-15 14:14:28.580 [vent.ItemStateChangedEvent] - KitchenSensorArmedTripped changed from 0 to 1
2018-05-15 14:14:28.584 [vent.ItemStateChangedEvent] - KitchenArmedTripped changed from 0 to 1
2018-05-15 14:14:28.621 [INFO ] [marthome.model.script.Kitchen Motion] - KitchenArmedTripped.state=1
2018-05-15 14:14:28.629 [ome.event.ItemCommandEvent] - Item 'KitchenLight' received command ON
2018-05-15 14:14:28.648 [vent.ItemStateChangedEvent] - KitchenLight changed from OFF to ON
2018-05-15 14:14:28.653 [vent.ItemStateChangedEvent] - KitchenSensorLastTrip changed from 2018-05-15T13:25:46.000-0400 to 2018-05-15T14:14:28.000-0400
2018-05-15 14:14:28.657 [ome.event.ItemCommandEvent] - Item 'Kitchen_TIMER_5m_OFF' received command ON
2018-05-15 14:14:28.690 [vent.ItemStateChangedEvent] - KitchenTripped changed from 2018-05-15T13:25:46.000-0400 to 2018-05-15T14:14:28.000-0400
2018-05-15 14:14:28.693 [vent.ItemStateChangedEvent] - KitchenSensorTripped changed from CLOSED to OPEN
2018-05-15 14:14:28.699 [vent.ItemStateChangedEvent] - Kitchen_TIMER_5m_OFF changed from OFF to ON
2018-05-15 14:14:28.734 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from NONE to PENDING
2018-05-15 14:14:28.929 [vent.ItemStateChangedEvent] - KitchenStatus changed from OFF to ON
2018-05-15 14:14:28.959 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from PENDING to SUCCESS
2018-05-15 14:15:00.147 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from SUCCESS to NONE
2018-05-15 14:19:28.698 [ome.event.ItemCommandEvent] - Item 'Kitchen_TIMER_5m_OFF' received command OFF
2018-05-15 14:19:28.711 [vent.ItemStateChangedEvent] - Kitchen_TIMER_5m_OFF changed from ON to OFF
2018-05-15 14:19:28.729 [INFO ] [marthome.model.script.Kitchen Motion] - Kitchen timer off
2018-05-15 14:19:28.743 [INFO ] [marthome.model.script.Kitchen Motion] - KitchenArmedTripped.state=1
2018-05-15 14:19:28.753 [ome.event.ItemCommandEvent] - Item 'Kitchen_TIMER_10s_OFF' received command ON
2018-05-15 14:19:28.764 [vent.ItemStateChangedEvent] - Kitchen_TIMER_10s_OFF changed from OFF to ON
2018-05-15 14:19:39.720 [ome.event.ItemCommandEvent] - Item 'Kitchen_TIMER_10s_OFF' received command OFF
2018-05-15 14:19:39.745 [vent.ItemStateChangedEvent] - Kitchen_TIMER_10s_OFF changed from ON to OFF
2018-05-15 14:19:39.755 [INFO ] [marthome.model.script.Kitchen Motion] - No Motion Detected.  Turning off kitchen
2018-05-15 14:19:39.764 [ome.event.ItemCommandEvent] - Item 'KitchenLight' received command OFF
2018-05-15 14:19:39.778 [vent.ItemStateChangedEvent] - KitchenLight changed from ON to OFF
2018-05-15 14:19:39.801 [vent.ItemStateChangedEvent] - KitchenTarget changed from 1 to 0
2018-05-15 14:19:39.835 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from NONE to PENDING
2018-05-15 14:19:40.002 [vent.ItemStateChangedEvent] - KitchenStatus changed from ON to OFF
2018-05-15 14:19:40.033 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from PENDING to SUCCESS
2018-05-15 14:19:53.380 [vent.ItemStateChangedEvent] - KitchenLastPollSuccess changed from 2018-05-15T14:12:43.000-0400 to 2018-05-15T14:19:53.000-0400
2018-05-15 14:20:11.112 [vent.ItemStateChangedEvent] - KitchenDeviceStatus changed from SUCCESS to NONE

Rules:

rule "Kitchen motion sensor fires"
    when
        Item KitchenArmedTripped changed to 1 

    then 
        logInfo("Kitchen Motion", "KitchenArmedTripped.state=" + KitchenArmedTripped.state)
        sendCommand(KitchenLight, ON)
        Kitchen_TIMER_5m_OFF.sendCommand(ON)
end

rule "Kitchen_TIMER_5m_OFF changed to  OFF"
    when
        Item Kitchen_TIMER_5m_OFF changed to OFF
    then
		logInfo("Kitchen Motion", "Kitchen timer off")
		logInfo("Kitchen Motion", "KitchenArmedTripped.state=" + KitchenArmedTripped.state)
	    Kitchen_TIMER_10s_OFF.sendCommand(ON)
end


rule "Kitchen_TIMER_10s_OFF changed to OFF"
    when
        Item Kitchen_TIMER_10s_OFF changed to OFF
    then
		if (KitchenArmedTripped.state == 0) {
			logInfo("Kitchen Motion", "Kitchen Motion still detected, reset timer.")
			Kitchen_TIMER_10s_OFF.sendCommand(ON)
		} else {
	           logInfo("Kitchen Motion", "No Motion Detected.  Turning off kitchen")
	           sendCommand(KitchenLight, OFF)
		}
end

Timer items:

Switch Kitchen_TIMER_5m_OFF 							(Virtuals)	{ expire="5m,command=OFF" }
Switch Kitchen_TIMER_10s_OFF 							(Virtuals)	{ expire="10s,command=OFF" }

Motion sensor items:

	/* Device - Kitchen Motion Sensor */

	DateTime KitchenTripped		"Last Tripped [%1$ta, %1$tm/%1$te %1$tR]" <calendar> 	(Kitchen) {mios="unit:house,device:208/service/SecuritySensor1/LastTrip"}
	Switch   KitchenArmed 		"Kitchen Motion Sensor Armed" 			  <shield-1>	(Kitchen) {mios="unit:house,device:208/service/SecuritySensor1/Armed"}
	String   KitchenArmedTripped "Kitchen Sensor Armed/Tripped [%s]" 					(Kitchen) {mios="unit:house,device:208/service/SecuritySensor1/ArmedTripped"}
	Switch   KitchenMotion 		"Motion Detected" 				  		  				(Kitchen) {mios="unit:house,device:208/service/SecuritySensor1/Tripped"}
	DateTime MotionTimeOn 		"Kitchen ON[%s]" 										(Kitchen)
	DateTime MotionTimeOff 		"Kitchen OFF[%s]" 										(Kitchen)
	Number   KitchenLux			"Kitchen Lux [%.4f]" 									(Kitchen) {mios="unit:house,device:210/service/LightSensor1/CurrentLevel"}
	Number   KitchenHumid		"Kitchen Humid [%.4f]" 					<humidity>		(Kitchen) {mios="unit:house,device:211/service/HumiditySensor1/CurrentLevel"}
	Number   KitchenTemp		"Kitchen Temp [%.1f F]" 				<temperature>	(Kitchen) {mios="unit:house,device:209/service/TemperatureSensor1/CurrentTemperature"}

If you keep moving during the five minutes, does KitchenArmedTripped actually change state or does it just keep getting updated to 1 over and over? If it doesn’t actually change then your Rule never fires again so the Expire timer doesn’t get reset.