Cancel timer doesn`t work

hi, I used the window open timer from the tutorial and applied it to my environment.
unfortunately I still get open door messages altough the doors are closed. I think the cancel timer doesn`t shoot correctly. can someone please double check?

the logs:

2018-03-29 20:32:50.771 [GroupItemStateChangedEvent] - Door changed from OPEN to CLOSED through Garagentor_links
2018-03-29 20:32:50.943 [INFO ] [eclipse.smarthome.model.script.Rules] - canceltimer {Garagentor_links=org.eclipse.smarthome.model.script.internal.actions.TimerImpl@1453fb}
2018-03-29 20:34:18.622 [INFO ] [eclipse.smarthome.model.script.Rules] - Fenster Garagentor_links 10 Min. offen

the rule:

import java.util.Map

val Map<String, Timer> OpenWindowTimers = newHashMap

val Functions$Function2<ContactItem, Map<String, Timer>, Boolean> checkOpenWindow = [
  Door,
  timerMap |

  val String myTimerKey = Door.name.toString
  //val Number temp = Temperature_outside.toString 

  if (Door.state == CLOSED) {
    //if (timerMap.get(myTimerKey) !== null) timerMap.get(myTimerKey).cancel()  //this line should do the same as the next one
    timerMap.get(myTimerKey)?.cancel
    logInfo("Rules", "canceltimer " + timerMap)
  } else if (Door.state == OPEN && Temperature_outside.state <= 5) {
    timerMap.put(myTimerKey, createTimer(now.plusMinutes(2)) [|
      timerMap.put(myTimerKey, null)

      logInfo("Rules", "Fenster " + Door.name.toString + " 10 Min. offen")
      Thread::sleep(100)
    ])
  }
else if (Door.state == OPEN && Temperature_outside.state > 5 && Temperature_outside.state < 15) {
    timerMap.put(myTimerKey, createTimer(now.plusMinutes(2)) [|
      timerMap.put(myTimerKey, null)

      logInfo("Rules", "Fenster " + Door.name.toString + " 30 Min. offen")
      Thread::sleep(100)
    ])
  }

  true
]

rule "Fenster check"
when
  Item Door received update
then
  Thread::sleep(250) // this gives the persistence service time to store the last update

  val lastUpdatedDoor = Door.members.filter[s|s.lastUpdate("mapdb") !== null].sortBy[lastUpdate("mapdb")].last as ContactItem

  checkOpenWindow.apply(lastUpdatedDoor, OpenWindowTimers)
end

Rather than try to find the problem I’m going to suggest an alternative implementation.

If you are on 2.2 release change your Rule to:

rule "Fenster check"
when
    Item Door1 changed or
    Item Door2 changed or
    ...
then
    val lastUpdateDoor = triggeringItem
    ...

If on a recent 2.3 snapshot then you can use:

rule "Fenster check"
when
    Member of Door changed
then
    val lastUpdateDoor = triggeringItem
    ...

Then I’d trade this rather high degree of complexity in your Rules file for a few extra Items and Design Pattern: Expire Binding Based Timers coupled with Design Pattern: Associated Items.

Group:Switch DoorTimers

Contact Door1 (Door) ...
Switch Door1_Timer (DoorTimers) { expire="2m,command=OFF" }
...

Then your Rule greatly simplifies to:

rule "Fenster check"
when
    Item Door1 changed or
    Item Door2 changed or
    ...
then
    val timerItem = DoorTimers.members.findFirst[ door | door.name == triggeringItem.name +"_Timer" ]
    if(triggeringItem.state == CLOSED)  timerItem.postUpdate(OFF) // cancel timer
    else timerItem.sendCommand(ON) // set the timer
end

rule "Fenster timer"
when
    Item Door1_Timer received command OFF or
    Item Door2_Timer received command OFF or
    ...
then
    if(Temperature_outside.state <= 5) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer", "") + " 10 Min. offen")
    }
    else if(Temperature_outside.state > 5 && Temperature_outside.state < 15) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer", "") + " 30 Min. offen")
    }
end

No more maps, no more lambdas, no more sleeps, no more timers, half as many lines of code, and fewer levels of indentation. I think it is a fair trade for n more Items to implement the timers.

1 Like

hi Rich,
I was dealing already with your design patterns but I didn´t achieve what I wanted.
my idea was to have two different timers depending on the temperature, because I use that rule mainly for beeing remembered if someone forgot to close the window after ventilating the room. If it´s below 5 deg Celcius I allow the window to be open for 5 minutes, if it´s 5 to 15 deg I allow 30 minutes, above 15 deg my heating is switched off anyway and I don`t care about open windows.

I gave your rule a try and split the rule “Fenster timer” in two rules:

//Items
Contact Door1 (Door)
Switch Door1_Timer1 (DoorTimers) { expire="2m,command=OFF" }
Switch Door1_Timer2 (DoorTimers) { expire="3m,command=OFF" }
rule "Fenster timer1"
when
    Item Door1_Timer1 received command OFF 
    
then
    if(Temperature_outside.state <= 5) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer", "") + " 10 Min. offen")
    }
    
end

rule "Fenster timer2"
when
    Item Door1_Timer2 received command OFF
then
    if(Temperature_outside.state > 5 && Temperature_outside.state < 15) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer", "") + " 30 Min. offen")
    }
end

the rules fail because the val timerItem requires a special namingconvention “_Timer” and ignores “_Timer1”
my Contact Items have different names, do I also have to rename them to Door1 to x?
the rule indeed simplifies things but on the other hand the item list gets thicker and thicker.
any idea how to implement two different timers for one item?

If that was your intent, that isn’t want l what you implemented. In both cases you set a timer for 2 minutes in the original code which is what I used as a guide.

You can do the same with Expire based timers. You would have a second timer Item and then use the name of the timer Item to send the right alert in the timer rule. Pretty much as you have.

I recommend using Design Pattern: Human Readable Names in Messages.

The Associated Item DP requires the items to bed named do you can easily created the name of an associated Item for there name of another one. You don’t have to change the name of your other contact items but you need to be consistent in how you name the timer items. Above I appended “_Timer”.

Like I said on the outset, you are trading additional items in exchange for reduced complexity in Rules. But because items are relatively simple it is more than a fair trade. If it is a code between a two new items per window versus two timers, a lambda, and a Map, the choice is simple. If add 30 or 40 new items to avoid that complexity in rules.

But to answer your question, you can only use one timer Item if the amount if time remains the same for both uses.

If that was your intent, that isn’t want l what you implemented. In both cases you set a timer for 2 minutes in the original code which is what I used as a guide.

my fault, next time I try to explain my use-case in more details. I´ve set both timers to 2 minutes for testing purposes only, didn´t want to wait 15 or 30 minutes each time I test the rules.
I implemented your recommendations but there still seems to be a bug. in the rule “Fenster check” you cancel the timer when a window closes

timerItem.postUpdate(OFF)

but this triggers rule “Fenster timer” (because Timer received command OFF) and reports a message which it shouldn´t do?

Using postUpdate will not trigger a rule that is using received command as the trigger. That is why we have both a received command an received update rule trigger. The “Fenster timer1” rule will only trigger when the Door1_Timer1 Item received an OFF command and since the only thing that is sending an OFF command to that Item is the expire binding that Rule should only trigger when the expire timer times out.

The postUpdate is used to cancel the timer.

So if that Rule is triggering then it means you are using Door1_Timer1.sendCommand(OFF) somewhere else in your rules or failing to cancel the timer with a postUpdate(OFF) when the door closes.

Add some logging to “Fenster check” to make sure that the postUpdate(OFF) is being called when the door closes.

Also look in events.log to make sure that all the Items are changing state as you expect.

this is what my log looks like, immediately after the Timer changes to OFF, it switches back to ON:

> 2018-04-03 22:40:05.502 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'fensteroffencheck.rules'
> 
> 2018-04-03 22:40:07.039 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'fensteroffencheck.rules'
> 
> ==> /var/log/openhab2/events.log <==
> 
> 
> 2018-04-03 22:40:16.406 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor changed from CLOSED to OPEN
> 
> 2018-04-03 22:40:18.737 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer1' received command ON
> 
> 2018-04-03 22:40:18.744 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer2' received command ON
> 
> ==> /var/log/openhab2/openhab.log <==
> 
> 2018-04-03 22:40:18.745 [INFO ] [eclipse.smarthome.model.script.Rules] - Fenster check Wohnzimmer_Terrassentuer_Sensor set timer
> 
> ==> /var/log/openhab2/events.log <==
> 
> 2018-04-03 22:40:18.768 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer1 changed from OFF to ON
> 
> 2018-04-03 22:40:18.774 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer2 changed from OFF to ON
> 
> 2018-04-03 22:40:37.436 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor changed from OPEN to CLOSED
> 
> 2018-04-03 22:40:38.499 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer2 changed from ON to OFF
> 
> ==> /var/log/openhab2/openhab.log <==
> 
> 2018-04-03 22:40:38.497 [INFO ] [eclipse.smarthome.model.script.Rules] - Fenster check Wohnzimmer_Terrassentuer_Sensor cancel timer
> 
> ==> /var/log/openhab2/events.log <==
> 
> 2018-04-03 22:40:38.525 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer1 changed from ON to OFF
> 
> 2018-04-03 22:40:38.536 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer1' received command ON
> 
> 2018-04-03 22:40:38.548 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer2' received command ON
> 
> 2018-04-03 22:40:38.555 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer1 changed from OFF to ON
> 
> ==> /var/log/openhab2/openhab.log <==
> 
> 2018-04-03 22:40:38.557 [INFO ] [eclipse.smarthome.model.script.Rules] - Fenster check Wohnzimmer_Terrassentuer_Sensor set timer
> 
> ==> /var/log/openhab2/events.log <==
> 
> 2018-04-03 22:40:38.569 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer2 changed from OFF to ON
> 
> 2018-04-03 22:41:39.169 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer1' received command OFF
> 
> 2018-04-03 22:41:39.182 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer1 changed from ON to OFF
> 
> 
> ==> /var/log/openhab2/events.log <==
> 2018-04-03 22:42:39.185 [ome.event.ItemCommandEvent] - Item 'Wohnzimmer_Terrassentuer_Sensor_Timer2' received command OFF
> 
> 2018-04-03 22:42:39.206 [vent.ItemStateChangedEvent] - Wohnzimmer_Terrassentuer_Sensor_Timer2 changed from ON to OFF
> 
> ==> /var/log/openhab2/openhab.log <==
> 
> 2018-04-03 22:42:39.206 [INFO ] [eclipse.smarthome.model.script.Rules] - Fenster Wohnzimmer_Terrassentuer_Sensor 30 Min. offen

my item look like this:

Contact Wohnzimmer_Terrassentuer_Sensor "Terrassentür [MAP(garage.map):%s]" <door> (Door) {channel="zwave:device:4dfe2711:node16:sensor_door"}
        Switch Wohnzimmer_Terrassentuer_Sensor_Timer1 (DoorTimers) { expire="1m,command=OFF" }
        Switch Wohnzimmer_Terrassentuer_Sensor_Timer2 (DoorTimers) { expire="2m,command=OFF" }

this is my rule:

rule "Fenster check"
when
    Item Wohnzimmer_Terrassentuer_Sensor changed or
    Item Kueche_Terrassentuer_Sensor changed or
    Item Waschkueche_Fenster_Sensor changed or
    Item Gaestezimmer_Fenster_Sensor changed or
    Item Schlafzimmer_Balkon changed or
    Item Keller_Katzenzimmer_Sensor changed or
    Item Keller_Kelleraufgang_Sensor changed or
    Item Garagentor_links changed or
    Item Garagentor_rechts changed   
    
then
    val timerItem1 = DoorTimers.members.findFirst[ Door | Door.name == triggeringItem.name +"_Timer1" ]
    val timerItem2 = DoorTimers.members.findFirst[ Door | Door.name == triggeringItem.name +"_Timer2" ]
    Thread::sleep(500)
    if(triggeringItem.state == CLOSED) { 
        Thread::sleep(500)
        timerItem1.postUpdate(OFF) // cancel timer1
        timerItem2.postUpdate(OFF) // cancel timer2
        logInfo("Rules", "Fenster check " + triggeringItem.name + " cancel timer") 
     } 
    else 
        Thread::sleep(500)
        timerItem1.sendCommand(ON) // set the timer1
        timerItem2.sendCommand(ON) // set the timer2
        logInfo("Rules", "Fenster check " + triggeringItem.name + " set timer") 
    end

rule "Fenster timer1"
when
    Item Garagentor_links_Timer1 received command OFF or
    Item Garagentor_rechts_Timer1 received command OFF or
    Item Kueche_Terrassentuer_Sensor_Timer1 received command OFF or
    Item Waschkueche_Fenster_Sensor_Timer1 received command OFF or
    Item Gaestezimmer_Fenster_Sensor_Timer1 received command OFF or
    Item Schlafzimmer_Balkon_Timer1 received command OFF or
    Item Keller_Katzenzimmer_Sensor_Timer1 received command OFF or
    Item Keller_Kelleraufgang_Sensor_Timer1 received command OFF or
    Item Wohnzimmer_Terrassentuer_Sensor_Timer1 received command OFF
    
then
    if(Temperature_outside.state <= 5) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer1", "") + " 10 Min. offen")
        }
    
end

rule "Fenster timer2"
when
    Item Garagentor_links_Timer2 received command OFF or
    Item Garagentor_rechts_Timer2 received command OFF or
    Item Kueche_Terrassentuer_Sensor_Timer2 received command OFF or
    Item Waschkueche_Fenster_Sensor_Timer2 received command OFF or
    Item Gaestezimmer_Fenster_Sensor_Timer2 received command OFF or
    Item Schlafzimmer_Balkon_Timer2 received command OFF or
    Item Keller_Katzenzimmer_Sensor_Timer2 received command OFF or
    Item Keller_Kelleraufgang_Sensor_Timer2 received command OFF or
    Item Wohnzimmer_Terrassentuer_Sensor_Timer2 received command OFF
then
    if(Temperature_outside.state > 5 && Temperature_outside.state < 30) {
        logInfo("Rules", "Fenster " + triggeringItem.name.replace("_Timer2", "") + " 30 Min. offen")
        }
end


Don’t mix the event.log and openhab.log. It is easier for me to look at them in parallel then all mixed up like that.

If the timer is immediately turning back on it means “Fenster check” is triggering again and setting the timer to ON again. Add logging to verify that is indeed what is happening and to try and figure out why.