I have noticed that there are a lot of questions about how to turn a light off x seconds after a motion detector turns it on. This can be very confusing, involving Timers, getting the light to stay on when motion is re-detected, what if the sensor doesn’t send OFF? and so on. You need a global Timer for every light, you have to check to see if the Timer exists, etc.
I have written a simple lambda function that takes care of all of this. No more global timers, no more checking, setting to null whatever.
Here it is:
import org.eclipse.xtext.xbase.lib.Functions
val Map<String, Timer> LightingTimers = newHashMap //used for motion detection timers
val Functions$Function4<GenericItem, Number, Number, Map<String, Timer>, Boolean> MakeLightTimer = [ //function (lambda) to create/reschedule/cancel timer (call with time set to 0 to cancel)
item,
level,
time,
timers |
var String timerKey = item.name
if (timers.containsKey(timerKey)) {
var Timer timerTmp = timers.get(timerKey)
if(time == 0) {
timerTmp.cancel()
timers.remove(timerKey)
logInfo("Light Timer Function", timerKey + " Timer cancelled" )
}
else {
timerTmp.reschedule(now.plusSeconds(time))
logInfo("Light Timer Function", timerKey + " Motion detected, Timer recheduled for " + time + " more seconds" )
}
}
else {
if(time != 0) {
if(level == 0) sendCommand(item, OFF)
else if(level == 100) sendCommand(item, ON)
else sendCommand(item, level.intValue)
logInfo("Light Timer Function", timerKey + " Motion detected, Light set to: " + level)
timers.put(timerKey, createTimer(now.plusSeconds(time)) [|
logInfo("Light Timer Function", timerKey + " Motion over, Light turning OFF" )
sendCommand(item, OFF)
timers.remove(timerKey)])
}
}
true
]
You use it like this:
var int LandingDelay = 60 // 60 seconds
var int landingNightBrightness = 10
rule "Landing LA Motion Detection"
when
Item AeonMS6MotionLA changed
then
// Landing LA Aeon detected motion
logInfo("Aeon Motion", "Landing Motion Detected!" )
logInfo("Aeon Motion", "Landing Aeon LA Motion Sensor is: " + AeonMS6MotionLA.state)
if (LandingMotionEnable.state == OFF) { // if motion disable flag is set - do nothing
logInfo("Landing Light", "Landing Light Motion Disabled - ignoring" )
return false
}
if (AeonMS6MotionLA.state == OPEN || AeonMS6MotionLA.state == ON) {
if(now.getHourOfDay() >= 1 && now.getHourOfDay() <= 6) { //between 1am and 7am (note hour 6 = 7am)
MakeLightTimer.apply(landingMain, landingNightBrightness, LandingDelay, LightingTimers)
}
else {
MakeLightTimer.apply(landingMain, 100, LandingDelay, LightingTimers)
}
}
end
Where AeonMS6MotionLA is a motion detector with an Item type of Contact or Switch, and landingMain is a light with Item type of Switch or Dimmer (if it’s a Switch, just use 0 and 100 as the levels).
To cancel an existing timer, call “MakeLightTimer.apply(landingMain, anything, 0, LightingTimers)” note time is set to 0 (level can be any number, it isn’t used if you are cancelling the timer). Nothing happens if the timer doesn’t exist, otherwise it is cancelled.
This is using OH2 syntax, but the syntax is almost identical for OH1, see https://community.openhab.org/t/reusable-functions-a-simple-lambda-example-with-copious-notes/15888 for the difference.
This is the simplest possible example:
rule "Washroom W Motion Detection"
when
Item AeonMS6MotionW changed
then
// Washroom W Aeon detected motion
if (AeonMS6MotionW.state == OPEN || AeonMS6MotionW.state == ON)
MakeLightTimer.apply(washroomLight, 100, 300, LightingTimers) //turn washroom light on for 300 seconds (5 minutes)
end
if the device just sends an ON on motion detection (ie no OFF), the rule would be:
rule "Washroom W Motion Detection"
when
Item AeonMS6MotionW changed to ON
then
// Washroom W Aeon detected motion
MakeLightTimer.apply(washroomLight, 100, 300, LightingTimers) //turn washroom light on for 300 seconds (5 minutes)
Thread::sleep(5000) //may not be needed, if sensor has a lockout period
postUpdate(AeonMS6MotionW, OFF)
end
I just wrote this, and it seems to work fine, but I will no doubt find improvements/edge issues etc. if you have an improvement, feel free to share it.