Does this work? I’ve a vague recollection that I tried to reschedule a Timer from inside itself and it not working or not working consistently because you cannot reschedule a Timer that has expired and if you are inside the Timer body the Timer is by definition expired. It is one of the reasons I wrote Design Pattern: Recursive Timers. Maybe it works better now or the behavior changed. This would have been back in the 1.8 days I think.
This can be replaced with
alarmReminderTimer?.cancel
The ?
operator will cause the line to simply be skipped if alarmReminderTimer is null. It is one of the nice pieces of a syntactic sugar the Xtext language added that I actually like.
Since we are posting examples
I use Design Pattern: Expire Binding Based Timers, Design Pattern: Associated Items, Design Pattern: Human Readable Names in Messages, and Design Pattern: Separation of Behaviors (for the alerts) to make the rule generic for all my doors:
Items:
Group:Contact:OR(OPEN,CLOSED) gDoorSensors "The doors are [MAP(en.map):%s]"
<door>
Group:Number:SUM gDoorCounts "Open Doors [%d]"
<door>
Contact vFrontDoor "Front Door is [MAP(en.map):%s]"
<door> (gDoorSensors,gDoorCounts)
{ mqtt="<[mosquitto:entry_sensors/main/front_door:state:default]" }
Switch vFrontDoor_Timer
(gDoorsTimers, gResetExpire)
{ expire="1h,command=OFF" }
DateTime vFrontDoor_LastUpdate "Front Door [%1$tm/%1$td %1$tH:%1$tM]"
<time> (gDoorsLast)
// these three Items for remaining doors and windows
Rules:
rule "Keep track of the last time a door was opened or closed"
when
Item vGarageOpener1 changed from CLOSED to OPEN or
Item vGarageOpener1 changed from OPEN to CLOSED or
Item vGarageOpener2 changed from CLOSED to OPEN or
Item vGarageOpener2 changed from OPEN to CLOSED or
Item vFrontDoor changed from CLOSED to OPEN or
Item vFrontDoor changed from OPEN to CLOSED or
Item vBackDoor changed from CLOSED to OPEN or
Item vBackDoor changed from OPEN to CLOSED or
Item vGarageDoor changed from CLOSED to OPEN or
Item vGarageDoor changed from OPEN to CLOSED
then
val door = triggeringItem as ContactItem
// Update LastUpdate
val last = gDoorsLast.members.filter[dt | dt.name == door.name+"_LastUpdate"].head as DateTimeItem // Associated Items DP
last.postUpdate(new DateTimeType)
// Set/cancel the Timer
if(door.state == OPEN) sendCommand(door.name+"_Timer", "ON") // Associated Items DP, Expire Based Timers DP
else postUpdate(door.name+"_Timer", "OFF") // Associated Items DP, Expired Based Timers DP
// Log and alert
val StringBuilder msg = new StringBuilder
val doorName = transform("MAP", "en.map", door.name) // Human Readable Names in Messages
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"){ // Time of Day DP
alert = true
msg.append(" and it is night")
}
if(vPresent.state == OFF) { // Generic Based Presence Example
alert = true
msg.append(" and no one is home")
}
if(alert){
// create Timer and wait before sending alert (I've a bad sensor, this prevents a barrage of alerts when it starts flapping)
if(flappingTimers.get(triggeringItem.name) === null) {
flappingTimers.put(triggeringItem.name, createTimer(now.plusSeconds(1), [|
msg.append("!")
aAlert.sendCommand(msg.toString) // Separation of Behaviors DP
flappingTimers.put(triggeringItem.name, null)
]))
}
// Flapping, cancel the timer before the alert can be sent
else {
flappingTimers.get(triggeringItem.name).cancel
flappingTimers.put(triggeringItem.name, null)
logWarn(logName, triggeringItem.name + " is flapping!")
}
}
else logInfo(logName, msg.toString)
end
// Expire Based Timers DP
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
val doorName = transform("MAP", "en.map", triggeringItem.name) // Human Readable Names in Messages DP
aAlert.sendCommand(doorName + " has been open for over an hour") // Separation of Behaviors DP
if(vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED") { // Time of Day DP
triggeringItem.sendCommand(ON) // reschedule the timer, Expire Based Timer DP
}
end
Once I get back to the AIY, I’ll update my aAlert rule to also announce the message on the speaker.
I actually like this set of Rules because it illustrates how the DPs are not intended to be used in isolation. They can and should be used together. If you include the Generic Presence Detection tutorial, the above two rules use seven separate tutorials and examples (if you include the Design Pattern: Working with Groups in Rules which is really just a generic application of the way Groups are used in the other DPs).
The theory of operation is when a door changes state I update a DateTime Item to store when the last time the door was opened/closed.
If the door is opened I start a Timer. If the door is closed, I cancel the Timer.
The rest of the main rule is constructing and sending an alert message. I use a map file to convert the Item name into a more friendly name for the message, indicate whether the door was opened or closed, and then add additional warnings if it is night and/or no one is home.
If it is night or no one is home I want an alert. I’ve a bad door sensor that periodically goes nuts flapping between open and closed several times a second and it gets worse in the cold. So I added a little anti-flapping Timer code to wait a second before sending the alert. If the door changes state while this antiflapping timer is active we throw out the alert. Otherwise we send on the alert using the Separation of Behaviors DP.
Remember that Timer we set? When it goes OFF this second rule triggers where we again use the map to get a nicer version of the Item name and send an alert that the door has been open too long. If it is NIGHT or BED time, we reschedule the Timer so we get the alert repeatedly until the door closes or MORNING comes.
I’ve posted this before which is probably why it looks very similar to Chris’s.