You can use the Expire binding for this as well, but will want a separate Timer Item.
Here is an example from my rules (slightly simplified).
Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
{ mqtt="<[chimera:entry_sensors/main/front_door:state:default]" }
Switch vFrontDoor_Timer
{ expire="1h,command=OFF" }
rule "Set Door Open Timer"
when
Item vFrontDoor changed
then
if(vFrontDoor.state == OPEN) vFrontDoor_Timer.sendCommand(ON)
else vFrontDoor_Timer.postUpdate(OFF)
end
rule "Timer expired for a door"
when
Item vFrontDoor_Timer received command OFF
then
aAlert.sendCommand("The front door has been open for over an hour")
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") {
timer.sendCommand(ON) // reschedule the timer
}
end
If you have a lot of doors and windows you want to track, you might find my full entry rules to be informative. Note that the rules below require persistence to be set up with everyChange to work.
You can find my Time of Day rules and Items here and my Alerting Items and Rules here though the names of my Items have changed since I wrote that last one.
For an explanation of how I access the Timer and LastUpdate Items from the Door Item’s name see this.
Group:Contact:OR(OPEN,CLOSED) gDoorSensors "The doors are [MAP(en.map):%s]"
Group:DateTime:MAX gDoorsLast "The last door event was [%1$tm/%1$td %1$tH:%1$tM]"
Group:Switch:OR(ON, OFF) gDoorsTimers
Switch aGarageOpener1 "Garage Door Opener 1"
Contact vGarageOpener1 "Garage Door Opener 1 is [MAP(en.map):%s]"
(gDoorSensors)
{ mqtt="<[chimera:entry_sensors/main/garage/door1:state:default]" }
Switch vGarageOpener1_Timer
(gDoorsTimers)
{ expire="1h,command=OFF" }
DateTime vGarageOpener1_LastUpdate "Garage Door Opener 1 [%1$tm/%1$td %1$tH:%1$tM]"
(gDoorsLast)
Switch aGarageOpener2 "Garage Door Opener 2"
Contact vGarageOpener2 "Garage Door Opener 2 is [MAP(en.map):%s]"
(gDoorSensors)
{ mqtt="<[chimera:entry_sensors/main/garage/door2:state:default]" }
Switch vGarageOpener2_Timer
(gDoorsTimers)
{ expire="1h,command=OFF" }
DateTime vGarageOpener2_LastUpdate "Garage Door Opener 2 [%1$tm/%1$td %1$tH:%1$tM]"
(gDoorsLast)
Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
(gDoorSensors)
{ mqtt="<[chimera:entry_sensors/main/front_door:state:default]" }
Switch vFrontDoor_Timer
(gDoorsTimers)
{ expire="1h,command=OFF" }
DateTime vFrontDoor_LastUpdate "Front Door [%1$tm/%1$td %1$tH:%1$tM]"
(gDoorsLast)
Contact vBackDoor "Back Door is [MAP(en.map):%s]"
(gDoorSensors)
{ mqtt="<[chimera:entry_sensors/main/back_door:state:default]" }
Switch vBackDoor_Timer
(gDoorsTimers)
{ expire="1h,command=OFF" }
DateTime vBackDoor_LastUpdate "Back Door [%1$tm/%1$td %1$tH:%1$tM]"
(gDoorsLast)
Contact vGarageDoor "Garage Door is [MAP(en.map):%s]"
(gDoorSensors)
{ mqtt="<[chimera:entry_sensors/main/garage_door:state:default]" }
Switch vGarageDoor_Timer
(gDoorsTimers)
{ expire="1h,command=OFF" }
DateTime vGarageDoor_LastUpdate "Garage Door [%1$tm/%1$td %1$tH:%1$tM]"
(gDoorsLast)
import org.eclipse.xtext.xbase.lib.Functions
val logName = "entry"
val Functions$Function1<SwitchItem, Boolean> openGarage = [ opener |
var topic = "NA"
switch opener.name{
case "aGarageOpener1": topic = "actuators/garage1"
case "aGarageOpener2": topic = "actuators/garage2"
}
logInfo("entry", "Publishing ON to " + topic)
publish("chimera", topic, "ON")
// TODO send an alert if cerberos is down
true
]
rule "Garage Opener 1 Triggered"
when
Item aGarageOpener1 received command
then
openGarage.apply(aGarageOpener1)
end
rule "Garage Opener 2 Triggered"
when
Item aGarageOpener2 received command
then
openGarage.apply(aGarageOpener2)
end
rule "Keep track of the last time a door was opened or closed"
when
Item vGarageOpener1 changed or
Item vGarageOpener2 changed or
Item vFrontDoor changed or
Item vBackDoor changed or
Item vGarageDoor changed
then
Thread::sleep(100)
val door = gDoorSensors.members.filter[s|s.lastUpdate("mapdb") != null].sortBy[lastUpdate("mapdb")].last as ContactItem
// Update LastUpdate
val last = gDoorsLast.members.filter[dt | dt.name == door.name+"_LastUpdate"].head as DateTimeItem
last.postUpdate(new DateTimeType)
// Set/cancel the Timer
val timer = gDoorsTimers.members.filter[t | t.name == door.name+"_Timer"].head as SwitchItem
if(door.state == OPEN) timer.sendCommand(ON)
else timer.postUpdate(OFF)
// Log and alert
val StringBuilder msg = new StringBuilder
val doorName = transform("MAP", "en.map", door.name)
msg.append(doorName)
msg.append(" was")
msg.append(if(door.state == OPEN) " opened" else " closed")
var alert = false
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED"){
alert = true
msg.append(" and it is night")
}
if(vPresent.state == OFF) {
alert = true
msg.append(" and no one is home")
}
if(alert){
msg.append("!")
aAlert.sendCommand(msg.toString)
}
logInfo(logName, msg.toString)
end
rule "Timer expired for a door"
when
Item vGarageOpener1_Timer received command OFF or
Item vGarageOpener2_Timer received command OFF or
Item vFrontDoor_Timer received command OFF or
Item vBackDoor_Timer received command OFF or
Item vGarageDoor_Timer received command OFF
then
Thread::sleep(100)
val timer = gDoorsTimers.members.sortBy[lastUpdate("mapdb")].last as SwitchItem
val doorName = transform("MAP", "en.map", timer.name)
aAlert.sendCommand(doorName + " has been open for over an hour")
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") {
timer.sendCommand(ON) // reschedule the timer
}
end