Rule optimization: Window OPEN reminder

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 :wink:

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.

2 Likes