Design Pattern: Motion Sensor Timer

```php
Code goes here
```

Next try, slowly I feel ā€¦

``
import java.util.List
var List timers = newArrayList
var Timer shutoffTimer = null
var lastRun = now

rule ā€œSonbas Motion changed from OFF to ON 3 times in a minuteā€
when
Item Sonbas10b changed from OFF to ON or
Item Sonoff3 changed from OFF to ON
then
if(shutoffTimer !== null || Alarm.state == OFF || lastRun.isAfter(now.minusMinutes(2))) return;
if(timers.size < 2) {
val t = createTimer(now.plusMinutes(1), [ | timers.remove(t) ])
timers.add(t)
}

if(timers.size == 2) {
sendMail("xxx@gmail.com", ā€œSonbas10bā€, ā€œSonbas10b motionā€)
TH03.sendCommand(ON)
lastRun = now
logInfo(ā€œRuleInfoā€, ā€œSiren STARTā€)
shutoffTimer = createTimer(now.plusSeconds(30)) [|
TH03.sendCommand(OFF)
logInfo(ā€œRuleInfoā€, ā€œSiren STOPā€)
shutoffTimer = null
]
}
end``

I canā€™t tell what is going wrong.

You must have three back ticks. The ā€œphpā€ part is optional.

```

Then later in your code on a new line. There the back ticks must be on their own line.

Then close with three back ticks on their own line.

Ok will try it next time.

But more important would be how to reset the counter, when alarm was activated.
Now it counts the first time correctly to 3 but after that each new pir movement is triggering the alarm.
I would like to have the counter to 3 again.
Can you help please?

count = count +1
if(count > 3) count = 3

Very nice article!

I am thinking how to solve my bed room where I also have table with computer.

I have magnet contact on door, motion sensor, lights.

When I enter magnet switch on and if its dark and before midnight lights turn on.
I like switch them off by moving sensor, but ā€¦

If i leave room and keep door open, can detect no movement and switch lights off with timer off - OK

But If i am in room and not moving longer then timer (watching something on computer, lie on bed) light gets also off after timer expied

Like to turn on nightmode when its after midnight and movement detect - OK

But If I move in bed in the night, lights get on also because of movement ā€¦

Ani advice / idea how to tune it more?

For example in corridor its more easy - movement and Illumination less then X turn on and start counting 2mins. If another movement before 2 mins reset counter - keep lights on - if counter count to the end, turn lights off.

You may not be able to do this with the sensors you have. You can only make decisions on whether to turn on or off the light based on the information available:

  • time
  • door sensor
  • motion sensor

You need some other sensor or piece of information to tell OH that you are still in the room.

This one you might be able to deal with by having a special behavior that ignores the motion detection after a certain time of day.

I had the same problem, and solve it, placing the sensors in a position lower than the level of the bed

2 Likes

Simple and effective
Always go back to the hardware firstā€¦ If you can solve it there, it will save you a lot of time and headaches trying to solve the problem with overly complicated coding.
Good idea!!

Thnx for idea :slight_smile: I already ordered more motion sensors. So i guess this can be the way. More sensors in different positions (under table when u sit infront of PC, face to door + door magnet when u leave room, under the bed when you step out of bed from time to time etc) and you just have to make smart rules thro all of them ā€¦ but who will do battery maintenance :slight_smile:

Itā€™s not about adding many more, you just have to find the right position

@rlkoshak
Not sure if this has been pointed out (probably not since the original post has not been edited), or i mis-understood the original example, but there is a moderate flaw in the occupancyTimer logic. (also a syntax error, missing a close ) in your sample code :slight_smile:

By monitoring for a single state in the rool, there is a risk of a mis-fire on the ā€œOFFā€ segment of the timer caused by the following boundary case:

  • Person enters room, triggers motion
  • Person leaves the room, motion eventually stops - timer starts (i.e. 1 minute timer)
  • Person re-enters the room with less than [motion sensor reset time] remaining (i.e. 45 seconds later, 15 seconds remaining on timer - motion is back on now but timer is still ticking)
  • Timer expires while person is in the room and turns off the switches referenced in the timer

At this point, there is no recourse but to leave the room, wait for the motion sensor to reset, and re-enter the room. The switches will never turn back on as long as the room remains occupied.

My solution was to fire the rule on every update to the motion sensor, this has the boundary case that if motion remains active longer then the timer you could still turn the lights off while occupied - so to handle this, check the state before turning off the switches. If still occupied just expire the timer, it will restart when the sensor changes to off.

Not sure if there is a better solution - the below is working, but if you have any suggestions for improvement iā€™d love the feedback.

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

rule "Laundry Lights Off"
when
    Item Laundry_Motion received update  	// Run rule every time motion is updated
then
	// If no timer is set, create a new timer
    if(occupancyTimer === null) {
    	logInfo("Laundry","Motion change: Setting timer for " + timeoutMinutes + " minutes.")
        occupancyTimer = createTimer(now.plusMinutes(timeoutMinutes ), [|

			// Check motion sensor state - If it is off, it's safe to turn off the lights
			// Otherwise just expire the timer, because motion has persisted longer 
			// than the timeout, and the lights need to remain on
           	if(Laundry_Motion.state == OFF) {
           		Laundry1_Toggle.sendCommand(OFF)
           		Laundry2_Toggle.sendCommand(OFF)
           	} else {
           		logInfo("Laundry","Timer Expired but Motion still detected. Leaving lights ON")
           	}

			// Expire the timer
           	occupancyTimer = null
        ]) // End timer logic
    }
    
    // Timer is already running, but a change has been detected
    // Reset the timer to its original timeout
    else {
    	logInfo("Laundry","Motion change: Resetting timer for " + timeoutMinutes + " minutes.")
        occupancyTimer.reschedule(now.plusMinutes(timeoutMinutes))
    }
end
1 Like

The example Rules above purposefully trigger on received update ON or received command ON so that the Rule gets retriggered on that second ON when the person reenters the room and the timer gets rescheduled for a minute after this new ON update. With Expire Binding the timer gets set/reset based on the most recent update. In the Timer version the Timer gets explicetly rescheduled if it is already running.

The timer will only go OFF 5 minutes after the LAST ON update/command.

However, there is an assumption here that I donā€™t remember if I stated. The Timer time must be longer than the cool down period built into the motion sensor itself. For example, many/most motion sensors will trigger on motion and then go blind for a certain period of time and not report anything until that time has passed. The timer times must be longer than this blind period. Maybe this is what caused your problem?

The problem with using just received update is that most motion sensors will publish an OFF after that ā€œblindā€ period meaning that your actual timer to turn off the light will be built in blind period in motion sensor + timer time instead of just timer time.

Though some motion sensors never report OFF at all.

Without seeing your original code along with some logs I canā€™t tell you why you saw the behavior you experienced. But except for the typo Iā€™m pretty certain the Rules above work, assuming that the timeout time is long enough and the motion detector behaves normally.

In the normal case, you will never have a case where ā€œTimer Expired but Motion still detected.ā€ because the timer will be rescheduled further into the future when Laundry_Motion was updated to ON again.

@rlkoshak
That all makes sense.

My use case is specific to my particular motion sensors then. They do not have a ā€œblindā€ period, or if they do they do not publish an OFF and then another ON unless there is a break in motion.

If there is continued motion in the room for 1 hour, then they will remain in the ā€œONā€ state and send no updates for that full hour, so without the added logic that i added and posted above, thereā€™s really no way to avoid a timeout during occupancy in my case.

Indeed, the DP above depends on periodic ON updates.

But if you want the light to stay on for a certain number of minutes after motion is no longer detected, you can flip around the logic to only set the timer when OFF is received and cancel the timer when ON is received.

This makes it so the light goes off 1 minute after the motion sensor reports that there has been no motion.

var Timer occupancyTimer = null
val int timeoutMinutes = 1

rule "Motion stopped, set a timer to turn off the lights"
when
    Item Laundry_Motion received update
then
    // No motion detected, set a timer
    if(Laundry_Motion.state == OFF) {
        logInfo("Laundry", "Motion change: Setting timer for " + timeoutMinutes + " minutes.")
        occupancyTimer = createTimer(now.plusMinutes(timeoutMinutes, [ |
            Laundry1_Toggle.sendCommand(OFF)
            Laundry2_Toggle.sendCommand(OFF)
            occupancyTimer = null
        ])
    }
    else {
        occupancyTimer?.cancel
        occupancyTimer = null
    }
end

I canā€™t believe i didnā€™t think about cancelling instead of resetting. It has the same effect overall, but is a cleaner approach for sure.

Question though, the syntax:
occupancyTimer?.cancel
Is the ? a typo? or is that intentional and some syntax i donā€™t recognize? Iā€™ve just used timer.cancel() in the past.

The latter. Itā€™s a short cut way to

if(occupancyTimer !== null) occupancyTimer.cancel

Beauty :slight_smile:

Is it meant to be ā€˜[|ā€™ or ā€˜[ |ā€™ with createTimer?

Thanks

Either should work. I prefer to use the |