Debouncing a switch

Hello,

experimenting with my first rules here, and I have run into what looks like a switch debounce problem.

I have a (zwave) contact, and I want it to turn on a device when the contact opens, and shut it off when the contact closes. Should be simple:

rule "when front storm door opened turn on front christmas lights"
when
    Item FRONT_STORMDOOR_CONTACT received update
then
    {
        if (FRONT_STORMDOOR_CONTACT.state == OPEN)
            {
                logInfo("openhab","front storm door opened, turning on front christmas lights")
                sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,ON)
            }
        else
            {
                logInfo("openhab","front storm door closed, turning off front christmas lights")
                sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,OFF)
            }
    }
end

Problem is that when I trigger the contact switch, it’s actually executing two (or more!!) on/off messages every time the contact makes or breaks:

2016-03-01 18:46:46.950 [INFO ] [g.openhab.model.script.openhab] - front storm door opened, turning on front christmas lights
2016-03-01 18:46:46.995 [INFO ] [g.openhab.model.script.openhab] - front storm door opened, turning on front christmas lights
2016-03-01 18:46:51.735 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights
2016-03-01 18:46:51.748 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights
2016-03-01 18:46:56.103 [INFO ] [g.openhab.model.script.openhab] - front storm door opened, turning on front christmas lights
2016-03-01 18:46:56.137 [INFO ] [g.openhab.model.script.openhab] - front storm door opened, turning on front christmas lights
2016-03-01 18:47:04.247 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights
2016-03-01 18:47:04.458 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights

So my next thought was to manually debounce the switch in software using timers, but that isn’t really giving any better results:

var Timer StormDoorOpenDebounceTimer = null
var Timer StormDoorCloseDebounceTimer = null

rule "when front storm door opened for 300ms turn on front christmas lights"
when
    Item FRONT_STORMDOOR_CONTACT received update
then
    {
        if (FRONT_STORMDOOR_CONTACT.state == OPEN)
            {
                logInfo("openhab","front storm door opened, debouncing...")
                StormDoorOpenDebounceTimer = createTimer(now.plusMillis(300)) 
                [|
                    logInfo("openhab","front storm door opened for 300ms continuously, turning on front christmas lights")
                    sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,ON)
                ]                            
                StormDoorCloseDebounceTimer.cancel
            }
        else
            {
                logInfo("openhab","front storm door closed, debouncing...")
                StormDoorCloseDebounceTimer = createTimer(now.plusMillis(300))
                [|
                    logInfo("openhab","front storm door closed, turning off front christmas lights")
                    sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,OFF)
                ]
                StormDoorOpenDebounceTimer.cancel
            }
    }
end

Which then cranks out:

2016-03-01 18:48:47.029 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, debouncing...
2016-03-01 18:48:47.097 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, debouncing...
2016-03-01 18:48:47.332 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights
2016-03-01 18:48:47.400 [INFO ] [g.openhab.model.script.openhab] - front storm door closed, turning off front christmas lights

So my questions are…

  1. Is there a better way to make sure only one event is sent on a bouncy contact?
  2. If not, what am I doing wrong with my debounce code?

Thanks!!

Just dotting Is and crossing Ts here… the command is definitely being sent twice on my rule:

2016-03-01 22:38:53.490 [DEBUG] [.ZWaveBinarySwitchCommandClass:150 ]- NODE 84: Creating new message for application command SWITCH_BINARY_SET
2016-03-01 22:38:53.491 [DEBUG] [o.b.z.i.protocol.SerialMessage:113 ]- NODE 84: Creating empty message of class = SendData (0x13), type = Request (0x00)
2016-03-01 22:38:53.495 [DEBUG] [WaveController$ZWaveSendThread:1301]- NODE 84: Sending REQUEST Message = 01 0A 00 13 54 03 25 01 00 25 DF 6F
2016-03-01 22:38:53.508 [DEBUG] [b.z.i.p.s.SendDataMessageClass:38  ]- NODE 84: Sent Data successfully placed on stack.
2016-03-01 22:38:53.537 [DEBUG] [.ZWaveBinarySwitchCommandClass:150 ]- NODE 84: Creating new message for application command SWITCH_BINARY_SET
2016-03-01 22:38:53.538 [DEBUG] [o.b.z.i.protocol.SerialMessage:113 ]- NODE 84: Creating empty message of class = SendData (0x13), type = Request (0x00)
2016-03-01 22:38:53.558 [DEBUG] [b.z.i.p.s.SendDataMessageClass:73  ]- NODE 84: SendData Request. CallBack ID = 223, Status = Transmission complete and ACK received(0)
2016-03-01 22:38:53.561 [DEBUG] [WaveController$ZWaveSendThread:1364]- NODE 84: Response processed after 64ms/3975ms.
2016-03-01 22:38:53.562 [DEBUG] [WaveController$ZWaveSendThread:1301]- NODE 84: Sending REQUEST Message = 01 0A 00 13 54 03 25 01 00 25 E0 50
2016-03-01 22:38:53.573 [DEBUG] [b.z.i.p.s.SendDataMessageClass:38  ]- NODE 84: Sent Data successfully placed on stack.
2016-03-01 22:38:53.624 [DEBUG] [ApplicationCommandMessageClass:40  ]- NODE 84: Application Command Request (ALIVE:DONE)
2016-03-01 22:38:53.625 [DEBUG] [ApplicationCommandMessageClass:58  ]- NODE 84: Incoming command class HAIL

Whereas if I just manually toggle the switch from the GUI:

2016-03-01 22:41:09.939 [DEBUG] [.ZWaveBinarySwitchCommandClass:150 ]- NODE 84: Creating new message for application command SWITCH_BINARY_SET
2016-03-01 22:41:09.940 [DEBUG] [o.b.z.i.protocol.SerialMessage:113 ]- NODE 84: Creating empty message of class = SendData (0x13), type = Request (0x00)
2016-03-01 22:41:09.945 [DEBUG] [WaveController$ZWaveSendThread:1301]- NODE 84: Sending REQUEST Message = 01 0A 00 13 54 03 25 01 FF 25 F1 BE
2016-03-01 22:41:09.957 [DEBUG] [b.z.i.p.s.SendDataMessageClass:38  ]- NODE 84: Sent Data successfully placed on stack.
2016-03-01 22:41:10.085 [DEBUG] [b.z.i.p.s.SendDataMessageClass:73  ]- NODE 84: SendData Request. CallBack ID = 241, Status = Transmission complete and ACK received(0)
2016-03-01 22:41:10.089 [DEBUG] [WaveController$ZWaveSendThread:1364]- NODE 84: Response processed after 144ms/3975ms.
2016-03-01 22:41:10.297 [DEBUG] [ApplicationCommandMessageClass:40  ]- NODE 84: Application Command Request (ALIVE:DONE)
2016-03-01 22:41:10.298 [DEBUG] [ApplicationCommandMessageClass:58  ]- NODE 84: Incoming command class HAIL

Beyond that, because multiple commands are being sent the switch is sending a HAIL on each one, which makes the network super duper chatty. The manually triggered one only generates one HAIL from the device, as to be expected.

SOOOoooo looks like I really desperately need to debounce this one! Suggestions welcome, thanks!

Maybe you can use: https://github.com/openhab/openhab/wiki/Samples-Rules#how-to-create-a-rule-which-only-executes-some-code-if-a-value-does-not-change-for-a-certain-period-of-time

1 Like

It would be interesting to know the type (brand, model, etc.) of Z-wave device that you are using, as well as the item configuration for the FRONT_STORMDOOR_CONTACT item.

Contact bounce is something that happens on physical contacts/switches (relays, etc.) and should be handled close to the source (i.e. where the state of the physical switch is read) - meaning in your Z-Wave device. Clearly that is not an option for you, unless you have put together some kind of door sensor node yourself?

The basic definition of a bouncing contact/switch is that you will experience repeated state changes (OPEN, CLOSED, OPEN, etc.) until it settles in the correct state. Debouncing is basically a way to filter out the state changes until the final stage has been reached.

In your case, however, it seems that you consistently receive two updates of the same state (OPEN + OPEN, or CLOSED + CLOSED). All in all, I believe this is not a bounce/debounce problem but something else.

That said, you still need a solution to your problem, :slightly_smiling:

I think the simplest solution to your problem is one of the following alternatives:

ALTERNATIVE 1

rule "when front storm door opened turn on front christmas lights"
when
    Item FRONT_STORMDOOR_CONTACT changed to OPEN
then
    logInfo("openhab","front storm door opened, turning on front christmas lights")
    FRONTHALL_CHRISTMAS_LIGHTS.sendCommand(ON)
end

rule "when front storm door closed turn off front christmas lights"
when
    Item FRONT_STORMDOOR_CONTACT changed to CLOSED
then
    logInfo("openhab","front storm door closed, turning off front christmas lights")
    FRONTHALL_CHRISTMAS_LIGHTS.sendCommand(OFF)
end

The rules above triggers on state change instead of state update. Since you only have one state change from your device your rule will only be executed once. This is my recommended solution.

ALTERNATIVE 2

rule "when front storm door opened turn on front christmas lights"
when
    Item FRONT_STORMDOOR_CONTACT received update
then
    if (FRONT_STORMDOOR_CONTACT.state == OPEN) {
        if (FRONTHALL_CHRISTMAS_LIGHTS.state == OFF) {
            logInfo("openhab","front storm door opened, turning on front christmas lights")
            FRONTHALL_CHRISTMAS_LIGHTS.sendCommand(ON)
        }
    }
    if (FRONT_STORMDOOR_CONTACT.state == CLOSED) {
        if (FRONTHALL_CHRISTMAS_LIGHTS.state == ON) {
            logInfo("openhab","front storm door closed, turning off front christmas lights")
            FRONTHALL_CHRISTMAS_LIGHTS.sendCommand(OFF)
        }
    }
end

In the above rule you still trigger on state update, meaning that the rule will be executed twice, but you add a guard condition to check the state of the christmas light so that it is not turned on twice (which is really not possible, but still…).

Hope this helps!

1 Like

True - this is not really contact bounce. Messages will occasionally (or regularly) be repeated on a wireless link. Due to the assynchronous nature of wireless links this will happen even if the device isn’t systematically sending data twice (which might of course be the case here).

Sorry - that’s not answering the question I know, but I thought I’d mention it all the same :wink:

Now to the question…

I would have done the second thing you tried - with timers. However, you need to add a flag that is set to true when the timer first triggers to stop it retriggering - otherwise all you’ve done is to delay the sending of the second command by 300ms - not stopped it being sent twice.

eg, something like -:

then
    {
        if(state==1) {
             return;
        }

        if (FRONT_STORMDOOR_CONTACT.state == OPEN)
            {
                state = 1
                logInfo("openhab","front storm door opened, debouncing...")
                StormDoorOpenDebounceTimer = createTimer(now.plusMillis(300)) 
                [|
                    logInfo("openhab","front storm door opened for 300ms continuously, turning on front christmas lights")
                    sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,ON)
                    state = 0
                ]                            
                StormDoorCloseDebounceTimer.cancel
            }
        else
            {
                state = 1
                logInfo("openhab","front storm door closed, debouncing...")
                StormDoorCloseDebounceTimer = createTimer(now.plusMillis(300))
                [|
                    logInfo("openhab","front storm door closed, turning off front christmas lights")
                    sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,OFF)
                    state = 0
                ]
                StormDoorOpenDebounceTimer.cancel
            }
    }
end

This might not be correct rule syntax, and you need to initialise the ‘state’ variable to 0, but it gives you the idea I hope…

1 Like

It’s an aeon door/window sensor, DSB04(blahblahblah). Config is…

Contact FRONT_STORMDOOR_CONTACT  "Front storm door [MAP(zwave_aeon_contact.map):%s]"  (Group_Contacts, Group_Front_Hall, Group_Test) {zwave="113:command=BASIC"}
Number  FRONT_STORMDOOR_BATTERY     "Front storm door battery [%d %%]" (Group_Battery, Group_Front_Hall) {zwave="113:command=BATTERY"}
// definition for front storm door tamper switch should go here, but not until openhab is the master.

Possibly, though I’m not really sure what it could be. First intuition was to check for multiple zwave bindings in the addons folder… nope. Then I decided to watch zwave in debug mode while breaking the contact sensor, and sure enough it looks like the sensor is doing two individual transmits! (Same message, only last byte is different.) Can’t really fault openhab for responding to two individual events received on the radio!

2016-03-02 09:31:51.950 [DEBUG] [eController$ZWaveReceiveThread:1530]- Receive Message = 01 09 00 04 08 71 03 20 01 FF 56
2016-03-02 09:31:51.952 [DEBUG] [eController$ZWaveReceiveThread:1446]- Receive queue ADD: Length=1
2016-03-02 09:31:51.952 [DEBUG] [b.z.i.protocol.ZWaveController:1194]- Receive queue TAKE: Length=0
2016-03-02 09:31:51.953 [DEBUG] [o.b.z.i.protocol.SerialMessage:243 ]- Assembled message buffer = 01 09 00 04 08 71 03 20 01 FF 56
2016-03-02 09:31:51.953 [DEBUG] [b.z.i.protocol.ZWaveController:1195]- Process Message = 01 09 00 04 08 71 03 20 01 FF 56
2016-03-02 09:31:51.953 [DEBUG] [b.z.i.protocol.ZWaveController:194 ]- Message: class = ApplicationCommandHandler (0x04), type = Request (0x00), payload = 08 71 03 20 01 FF
2016-03-02 09:31:51.954 [DEBUG] [ApplicationCommandMessageClass:40  ]- NODE 113: Application Command Request (ALIVE:STATIC_VALUES)
2016-03-02 09:31:51.954 [DEBUG] [ApplicationCommandMessageClass:58  ]- NODE 113: Incoming command class BASIC
2016-03-02 09:31:51.955 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:73  ]- NODE 113: Received Basic Request
2016-03-02 09:31:51.955 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:77  ]- NODE 113: Basic Set sent to the controller will be processed as Basic Report
2016-03-02 09:31:51.955 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:106 ]- NODE 113: Basic report, value = 0xFF
2016-03-02 09:31:51.956 [DEBUG] [b.z.i.protocol.ZWaveController:648 ]- Notifying event listeners: ZWaveCommandClassValueEvent
2016-03-02 09:31:51.956 [DEBUG] [.z.internal.ZWaveActiveBinding:449 ]- ZwaveIncomingEvent
2016-03-02 09:31:51.956 [DEBUG] [.z.internal.ZWaveActiveBinding:466 ]- NODE 113: Got a value event from Z-Wave network, endpoint = 0, command class = BASIC, value = 255
2016-03-02 09:31:51.957 [DEBUG] [ApplicationCommandMessageClass:89  ]- Transaction not completed: node address inconsistent.
2016-03-02 09:31:52.001 [DEBUG] [.ZWaveBinarySwitchCommandClass:150 ]- NODE 84: Creating new message for application command SWITCH_BINARY_SET
2016-03-02 09:31:52.002 [DEBUG] [o.b.z.i.protocol.SerialMessage:113 ]- NODE 84: Creating empty message of class = SendData (0x13), type = Request (0x00)
2016-03-02 09:31:52.002 [DEBUG] [eController$ZWaveReceiveThread:1530]- Receive Message = 01 09 00 04 00 71 03 20 01 FF 5E
2016-03-02 09:31:52.003 [DEBUG] [b.z.i.protocol.ZWaveController:947 ]- Callback ID = 125
2016-03-02 09:31:52.004 [DEBUG] [b.z.i.protocol.ZWaveController:632 ]- Enqueueing message. Queue length = 1
2016-03-02 09:31:52.005 [DEBUG] [b.z.i.protocol.ZWaveController:1194]- Receive queue TAKE: Length=0
2016-03-02 09:31:52.005 [DEBUG] [eController$ZWaveReceiveThread:1446]- Receive queue ADD: Length=1
2016-03-02 09:31:52.004 [DEBUG] [WaveController$ZWaveSendThread:1241]- Took message from queue for sending. Queue length = 0
2016-03-02 09:31:52.006 [DEBUG] [o.b.z.i.protocol.SerialMessage:243 ]- Assembled message buffer = 01 09 00 04 00 71 03 20 01 FF 5E
2016-03-02 09:31:52.007 [DEBUG] [o.b.z.i.protocol.SerialMessage:243 ]- Assembled message buffer = 01 0A 00 13 54 03 25 01 FF 25 7D 32
2016-03-02 09:31:52.007 [DEBUG] [b.z.i.protocol.ZWaveController:1195]- Process Message = 01 09 00 04 00 71 03 20 01 FF 5E
2016-03-02 09:31:52.008 [DEBUG] [WaveController$ZWaveSendThread:1301]- NODE 84: Sending REQUEST Message = 01 0A 00 13 54 03 25 01 FF 25 7D 32
2016-03-02 09:31:52.008 [DEBUG] [b.z.i.protocol.ZWaveController:194 ]- Message: class = ApplicationCommandHandler (0x04), type = Request (0x00), payload = 00 71 03 20 01 FF
2016-03-02 09:31:52.010 [DEBUG] [ApplicationCommandMessageClass:40  ]- NODE 113: Application Command Request (ALIVE:STATIC_VALUES)
2016-03-02 09:31:52.011 [DEBUG] [ApplicationCommandMessageClass:58  ]- NODE 113: Incoming command class BASIC
2016-03-02 09:31:52.011 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:73  ]- NODE 113: Received Basic Request
2016-03-02 09:31:52.012 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:77  ]- NODE 113: Basic Set sent to the controller will be processed as Basic Report
2016-03-02 09:31:52.012 [DEBUG] [z.i.p.c.ZWaveBasicCommandClass:106 ]- NODE 113: Basic report, value = 0xFF
2016-03-02 09:31:52.013 [DEBUG] [b.z.i.protocol.ZWaveController:648 ]- Notifying event listeners: ZWaveCommandClassValueEvent
2016-03-02 09:31:52.013 [DEBUG] [.z.internal.ZWaveActiveBinding:449 ]- ZwaveIncomingEvent
2016-03-02 09:31:52.014 [DEBUG] [.z.internal.ZWaveActiveBinding:466 ]- NODE 113: Got a value event from Z-Wave network, endpoint = 0, command class = BASIC, value = 255

[quote]
That said, you still need a solution to your problem, :slightly_smiling:[/quote]

:ok_hand:

I think I’m going to try alternative 1; it’s the most straightforward and should be the easiest to debug in the future should something upset the apple cart. Though #2 (and Chris’ modification of my original debounce) I think would both work if push comes to shove.

(In case anyone is wondering, no I don’t still have my christmas lights up. That’s just the controller I was using over the holidays, and since it’s free it’s now in my stable of testing devices. I can live without knowing if my storm door is open for a few weeks. :smiley: )

This isn’t possible - well, it’s possible to have 2 bindings, but not possible for them both to work.

It’s not uncommon for devices to send the same packet twice - just to be sure it gets there. It’s also possible for repeat requests to be made because the stick receives the initial message from the sensor, but the sensor doesn’t get the ‘I got your message’ message, so it resends and you get it twice. This is normal.

(chuckle) Hey I only play like I know what I’m doing on the internet. :smiley:

Oh, didn’t know that, thanks! I presumed since zwave has a send/ack structure that devices wouldn’t do this. Guess not!

It seems not - without some sort of additional information (eg a source frame counter) it’s not possible for the stick, or the binding, to know if this was a repeat packet that was sent for a good reason, or if it was due to a retry or repeat requests at source…

I have had similar issues with some MQTT stuff

I’d do something like
`rule "Test"
when
Item FRONT_STORMDOOR_CONTACT changed from OPEN to CLOSED or
Item FRONT_STORMDOOR_CONTACT changed from CLOSED to OPEN
then
if (FRONT_STORMDOOR_CONTACT.state == OPEN)
{
logInfo(“openhab”,“front storm door opened, turning on front christmas lights”)
sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,ON)
}
else
{
logInfo(“openhab”,“front storm door closed, turning off front christmas lights”)
sendCommand(FRONTHALL_CHRISTMAS_LIGHTS,OFF)
}
end

I came to this thread on a search for how to debounce an openhab rule, and none of these answers are really “correct” in my mind, so I wrote my own version using locks. This will guarantee thread safety in case rules are run concurrently and is very simple to understand. Thought I’d post it in case other people come to this thread for the same reason.

In my case, I’m debouncing a very cheap 433 wireless sensor picked up by rfxcom which often sends 2/3/4 times depending on its mood. State change doesn’t work, as its stateless, and I do need to know when it is triggered again a few seconds later. I left the debug in so its easy to see how it works.

import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

var Lock lock = new ReentrantLock()

rule "switch"
when
    Item Undecoded received update "107366"
then
    // tryLock will acquire the lock if it is available, and fail fast otherwise.
    if (lock.tryLock()) {
        logInfo("contact", "Contact sensor tripped")
        Thread::sleep(300)
        lock.unlock()
    } else {
        logDebug("doors", "Contact sensor tripped, but ignored due to lock")
    }
end
2 Likes