Rule for Button Hold and Release on remote for increase/decreasing

Hello all,

I’m currently re-implementing all my zigbee devices, including the IKEA Tradfri remotes. I have the integrated via zigbee2mqtt and it works properly. However there are two sent payload-modes of the buttons.

  • If you just click a button on the remote shortly, the payload buttonXY_click ist sent
  • If hold the button, the payload buttonXY_hold is sent. When I then release the hold, the payload buttonXY_release is sent afterwards (–> so two payloads are sent here: the “hold” in the beginning and the “release” in the end)

Catching the “click” event is easy. The thing I’m wondering about is, how ideally to implement the hold feature so that an action is repeated within the holding timeframe.

In the past I used this

case "arrow_right_hold": {
  Bol_cycle = true
  timer = createTimer(now, [ |
    if(Bol_cycle && counter < 60 ){
      //cycle through this
      timer.reschedule(now.plusMillis(2500))
    }
  ])
  timer.cancel
}
case "arrow_right_release": {
  Bol_cycle = false
}

I used a switch case statement to catch the payload. Then a timer is launched and reschedules each time when it goes through the loop. When the release payload comes, the boolean is set to false and the cycle stops.
This solution worked, but by using the timer it is not very elegant. Does someone maybe know a better way?

Does it? You create the timer and then immediately cancel it. I suppose the timing may have worked out just right that the cancel runs after the timer starts executing but before the reschedule so that the cancel effectively becomes a noop.

But that is really just a trick of the timing. You should remove that cancel.

You could use an Expire based Timer instead but that is just moving things around, not really addressing anything.

I know of no better way to do this.

Thanks for the quick reply.

Yeah it worked as the time.cancel is outside the original timer. However I just put it in there out of precaution and unknowledge, as I wanted to be sure that the timer really stops after this inside loop. Not that there is always a new timer instance created and at the end there are hundreds of unaffected timers running…

Thanks for your input. If this is the way to go I keep it - it just looked like clumsily for me as a non coder :smiley:

Let me explain what is happening step by step so you understand why the timer.cancel doesn’t do what you think it does and is in fact a bad idea. It is only working for you because of a trick of the timing and it is not guaranteed to continue to work forever. In fact, I bet if you use now.plusMillis(10), a mere 10 milliseconds, it would break your Timer.

  1. now calculates the current time.

  2. A new lambda is created. A lambda is an object that can be called like a method. So you can assign a lambda to a variable or pass it to another method like you are doing here. Everything between the [ and ] makes up the lambda. When you create a lambda, it gets a copy of the full context where it is created so it will have all the variables that existed in the Rule prior to the creation of the lambda. This is why you can call timer.reschedule inside the lambda. It existed before the lambda was created.

  3. A Timer is created. Creating a Timer involves registering the lambda with the Quartz engine to execute at the passed in time. Once registered createTimer immediately returns.

  4. a. Because you scheduled the Timer to execute now it immediately starts to run the lamba

    b. At the same time, your Rule continues to run and immediately executes timer.cancel. 4.a. and 4.b happen in parallel and it is only a fluke that the Quartz engine started executing the lambda before the Rule executed the timer.cancel. Creating a Timer does not block the rest of the Rule from running until the Timer is done executing.