Timers and Multi-Press Button Rule in OpenHab 2.5.12-1 on x86

My requirement: To use a sonoff mini button to:
Turn on my bedroom lamp, and if I double, triple, quadruple pressed the lights got brighter
If the lights were on, all lights would be turned off

The problem I was having was setting and resetting timers in order to detect single, double, triple press of a button (Zigbee version of the Sonoff Button SNZB-01)

My problem: I wanted the code within a Timer to be able to interact with other code in my Rule.
My Solution: Was to create an Item that can persist across all rules and timers

Background: Things I have learned by using:

var Timer MyTimer = null

MyTimer = createTimer(now.plusSeconds(5)) [| DoSomethingClever]
  • The timer is a code block that is spawned into a sub-process and your environment variables are cloned into that environment. Therefore, any change on the variables within the timer code is not reflected in the original variables contained in the parent rule. The cloned variables are lost when the timer expires.
  • If you execute multiple createTimer you do not overwrite a timer, but create a new one and you end up with multiple timers running. Therefore you should be cancelling or rescheduling timers.
  • Using if(MyTimer === null) {doSomethingClever} is an unreliable test for the existance of a timer.
  • I also have no idea why you would use three = instead of two (but I tried both)

In my system, MyTime.reschedule and the MyTimer.isRunning test, result in an error in the log
MyTimer.cancel works fine.

Code like:
if(MyTimer.isRunning){DoSomethingClever}

cannot invoke method public abstract boolean org.eclipse.smarthome.model.script.actions.Timer.isRunning() on null

Here is the code for my management of lights in the master bedroom.
We have two LIFX Lamps and four downlights on a zigbee dimmer.
I use the light level within the house to decide if the bedlamps should come on and it is only the first person that evening will trigger the rule.
The downlights are much brighter than the lamps, and so they “follow” the lamps from 50% to 100%

Any advice is greatly appreciated…

// Bedroom Light Rules

//Global Variables
var Number BLduskTimerArmed  // Enable Movement to trigger Lamps
var Number BLbrightness      // Variable to hold current brightness of a lamp
var HSBType asHSBvalue

//Required Item "Switch BedroomButtons_Timer "Bedroom Buttons Timer"
var Timer PressTimer = null // Used for Sonoff Button - multi-press within 5 seconds

// --------------------------------------------------------------
rule "Sonoff Mini BedroomB1 cycle Bedroom Lights"
// --------------------------------------------------------------
when
    Item BedroomB1_Switch changed
then
    // Read current state of Lamp 1
    asHSBvalue = MasterBedroomLamp1_Color.state as HSBType
    BLbrightness = asHSBvalue.brightness
  
    if(BedroomButtons_Timer.state !== ON) {

        if(BLbrightness == 0) {
            MasterBedroomLamp1_Color.sendCommand("60,0,5")
            BedroomButtons_Timer.sendCommand(ON)
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
        }
        else {
            MasterBedroomLamp1_Color.sendCommand("60,0,0")
            MasterBedroomLamp2_Color.sendCommand("60,0,0")
        }  
    }   
    else { 
        
        PressTimer.cancel

        if(BLbrightness < 30) {
            MasterBedroomLamp1_Color.sendCommand("60,0,30")
            MasterBedroomLamp2_Color.sendCommand("60,0,30")
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
            return;
        } 
        
        if(BLbrightness < 60) {
            MasterBedroomLamp1_Color.sendCommand("60,0,60")
            MasterBedroomLamp2_Color.sendCommand("60,0,60")
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
            return;
        }

        if(BLbrightness < 100) {
            MasterBedroomLamp1_Color.sendCommand("60,0,100")
            MasterBedroomLamp2_Color.sendCommand("60,0,100")
            BedroomButtons_Timer.sendCommand(OFF)
        } 
    }
      
end

// --------------------------------------------------------------
rule "Sonoff Mini BedroomB2 cycle Bedroom Lights"
// --------------------------------------------------------------
when
    Item BedroomB2_Switch changed
then
    // Read current state of Lamp 2
    asHSBvalue = MasterBedroomLamp2_Color.state as HSBType
    BLbrightness = asHSBvalue.brightness
  
    if(BedroomButtons_Timer.state !== ON) {

        if(BLbrightness == 0) {
            MasterBedroomLamp2_Color.sendCommand("60,0,5")
            BedroomButtons_Timer.sendCommand(ON)
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
        }
        else {
            MasterBedroomLamp1_Color.sendCommand("60,0,0")
            MasterBedroomLamp2_Color.sendCommand("60,0,0")
        }  
    }   
    else { 
        
        logInfo("rules", "Timer Detected")
        PressTimer.cancel

        if(BLbrightness < 30) {
            MasterBedroomLamp1_Color.sendCommand("60,0,30")
            MasterBedroomLamp2_Color.sendCommand("60,0,30")
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
            return;
        } 
        
        if(BLbrightness < 60) {
            MasterBedroomLamp1_Color.sendCommand("60,0,60")
            MasterBedroomLamp2_Color.sendCommand("60,0,60")
            PressTimer = createTimer(now.plusSeconds(5)) [| BedroomButtons_Timer.sendCommand(OFF)]
            return;
        }

        if(BLbrightness < 100) {
            MasterBedroomLamp1_Color.sendCommand("60,0,100")
            MasterBedroomLamp2_Color.sendCommand("60,0,100")
            BedroomButtons_Timer.sendCommand(OFF)
        } 
    }
      
end

// --------------------------------------------------------------
rule "First Motion in Bedroom in low light then Turn Lamps on"
// --------------------------------------------------------------
when
    Item Movement_MasterBedroom changed to OPEN 
then
    if(StairsSensor_Illuminance.state < 4.0 && BLduskTimerArmed == 1){ 
            MasterBedroomLamp1_Color.sendCommand("60,0,30")
            MasterBedroomLamp2_Color.sendCommand("60,0,30")
            MasterBedroomLamp1_Temperature.sendCommand(92)
            MasterBedroomLamp2_Temperature.sendCommand(92)
            BLduskTimerArmed = 0
    }
end

// --------------------------------------------------------------
rule "Re-Arm near dusk and early evening"
// -------------------------------------------------------------
when
    Time cron "0 0 17,20 1/1 * ? *"
then
    BLduskTimerArmed = 1
end

// --------------------------------------------------------------
rule "Downlights follow the Lamps after they reach 50%"
// --------------------------------------------------------------
when
    Item MasterBedroomLamp1_Color changed
then
    asHSBvalue = MasterBedroomLamp1_Color.state as HSBType
    BLbrightness = asHSBvalue.brightness
    if (BLbrightness < 40){ MasterBedroomLights_LevelControl.sendCommand(0);return }
    if (BLbrightness < 50){ MasterBedroomLights_LevelControl.sendCommand(5);return }
    if (BLbrightness < 60){ MasterBedroomLights_LevelControl.sendCommand(10);return }
    if (BLbrightness < 70){ MasterBedroomLights_LevelControl.sendCommand(20);return }
    if (BLbrightness < 80){ MasterBedroomLights_LevelControl.sendCommand(30);return }
    if (BLbrightness < 90){ MasterBedroomLights_LevelControl.sendCommand(60);return }
    MasterBedroomLights_LevelControl.sendCommand(100)
end
1 Like

This isn’t true. You will see a lot of examples asking the lines of

var Timer my timer = null

...

    myTimer = create timer(now.plusSeconds(1), [ |
        ...
        myTimer = null

    ]

This approach is proven to work and had worked this way since the beginning of OH. But the trick is it only makes sense if the variables being changed are globals. If they are local to the rule, a new variable gets created every time the rule runs so the timer can’t change that variable for other runs of the rule.

Or only creating a new tinder if an old one doesn’t already exist.

Assuming the hat line of the timer’s lambda sets MyTimer to null, it’s reliable. Where problems come in is when users try to treat OH as if it were a real time system capable or reacting to events in milliseconds.What most likely happened, since you are on OH 2, is that a second event occurred two fast and you had two copies of the rule running at the same time. And when the second one ran, the first one had kit yet finished creating the timer.

To deal with that you’d need to put some sort of lock around the test for the existence of the timer and creation of it if necessary.

== is the equivalency operator. It essentially calls the .equals method on the Objects to see if they are equivalent.

=== is the identity operator. It tests to see if both operands are in fact pointing to the same space in memory. Use === when null is one of the operands because null isn’t an Object and doesn’t have a. equals method and null always points to memory address 0.

Use == everywhere else.

This is almost certainly the timing issue I just mentioned.

If you are asking for help this should be in the rules category. Tutorials and Solutions are informed for working examples.

Without seeing the events.log and logging from the rules it’s hard to offer much. Take a look at the Rule Latching design pattern as well as the How to Structure a Rule design pattern on the forum. Also take a look at the last few posts on the Gate Keeper design pattern thread where a very similar requirement is discussed and coded.

1 Like

Thank you for taking the time to review my code and assumptions. I am going to stick with using an ITEM as a flag as I think there will be some scope for other rules to access or triggered. I just tried to move this to the other section, but I don’t seem to be able. Thanks again for your help.