Rule mixup when triggered several times

RPi3, OH current snapshot
When someone pushes the button at the door a rule is triggered to ring the bell for one second. To do that, a bistable relay is set to on, waits one second, and is set to off. The bistable relay requires controll by push button. Push button on-off sets relay to on, push button on-off sets relay to off.

Items

Switch Tor_Klingel /* Receives the signal from the doorbell */
Switch Klingelsender {tinkerforge="uid=rGW, subid=out2"} /* Makes the push button to control the relay */

Rule

rule "Auto push button release - required for relay"
when
	Item Klingelsender received command ON
then
		Klingelsender.sendCommand(OFF)
end

rule "Door button pushed, ring the bell for one second"
when
 	Item Tor_Klingel received command ON
 then
	Klingelsender.sendCommand(ON)
 	createTimer(now.plusSeconds(1))
 	Klingelsender.sendCommand(ON)
 	
 end

Issue is that it happens (very seldom) that the rule gets mixed up when someone at the door pushes very often very quickly. Result of mixup is that the on and off gets inverted. The bell rings all the time and is interrupted for one second if the button at the door is pressed.

How could this be solved?
Is there a way to block that the rule is triggered a second time (for some time)?

For one, createTimer doesn’t work like you think it does. It is used to schedule a bit of code to execute sometime in the future and immediately returns. So, because you don’t supply any code to execute in the future, createTimer(now.plusSeconds(1)) is a noop (i line of code that doesn’t do anything at all).

The easiest way to implement this is to use the Expire binding but I don’t think that will work like you want. The Expire binding will keep the relay ON for 1 second after the last time the user pushes the button. If they push the button several times within a second then the buzzer will continue to buzz longer than one second. But if that behavior is OK then you don’t need any rules, just:

Switch Klingelsender { tinkerforge="uid-rGw, subid-out2", expire="1s,command=OFF" }

If you want to implement what you described you can use a Thread::sleep, though long sleeps can cause problems. Thread::sleep is the way to implement what you are trying to do with createTimer. It is OK if this is the only one you will use but in this case I’d recommend against it. It is possible that a very impatient individual could hit the button more than five times in one second which will cause the rest of your Rules to stop executing for one second. See Why have my Rules stopped running? Why Thread::sleep is a bad idea.

So to handle this properly, we need to use a Timer correctly.

var Timer klingelsender_timer = null

// I don't know this binding but this Rule may not be necessary if the device responds to only ON commands
rule "Auto push button release - required for relay"
when
	Item Klingelsender received command ON
then
		Klingelsender.sendCommand(OFF)
end

rule "Door button pushed, ring bell for one second"
when
    Item Tor_Klingel received command ON
then
    if(klingelsender_timer !== null) return; // ignore the button press if we are already buzzing

    Klingelsender.sendCommand(ON)

    klingelsender_timer = createTimer(now.plusSeconds(1), [ |
        Klingelsender.sendCommand(ON)
        klingelsender_timer = null
    ])
end

The Rule above exits if there is a timer already running. This effectively ignores button presses that occur while there is a Timer running. Then it sends the ON command to the relay. Then is schedules two lines of code to execute in one second to send the ON command to the relay again and set the timer to null.

1 Like

Sorry, I pasted the rule and deleted too much lines after createtTimer. The one I was using before was

rule "Door button pushed, ring the bell for one second"
when
 	Item Tor_Klingel received command ON
 then
	Klingelsender.sendCommand(ON)
 	createTimer(now.plusSeconds(1))[|
 	Klingelsender.sendCommand(ON)
        ]) 	
 end

The solution for the issue was both, a very short Thread::sleep together with the timer that is set to null et the end of the rule. The very short Thread::sleep is necessary to allow the physical relay to react on the pushbutton command. OH can execute the pushbutton commands quicker than the relay can handle. Thread::sleep of 150 milliseconds is enough for the relay to finish the closing/opening of the contact. This avoids that two commands are send from OH and only one is executed by the relay. That was the case when the push button at the door was pressed too quickly or exactly at the time when my bad rule from the first post ended. This led in the past to missed relay changes and thus to the funny inverted rule where the bell rings when it should not and vice versa.