Button Shim hold issue


#1

Hi,

I’ve just got a button shim, and I’m happy with it - I’ve just discovered an issue though… I’m using IFTTT webhooks to send a request when the button is pushed that turns my wifi lights on. When the button is held for 2 seconds, a request is sent to turn the wifi lights off. However, when I hold the button it send both the request to turn the lights off and turn the lights on. This means that the lights briefly go off and then come back on again! From what I can see, it’s registering both a short press and a hold when I hold the button - does anyone have any suggestions as to what’s causing this?

This is the section of my code in question (indents have been lost when copying but they are there):

@buttonshim.on_press(buttonshim.BUTTON_A)
def button_a(button, pressed):   
buttonshim.set_pixel(0, 255, 0)
time.sleep(1)
buttonshim.set_pixel(0, 0, 0) 
r = requests.post('https://maker.ifttt.com/trigger/lights_on/with/key/KEY, params={"value1":"none","value2":"none","value3":"none"})

@buttonshim.on_hold(buttonshim.BUTTON_A, handler=None, hold_time=2)
def handler(button):
buttonshim.set_pixel(255, 255, 0)
time.sleep(1)
buttonshim.set_pixel(0, 0, 0)
r = requests.post('https://maker.ifttt.com/trigger/lights_off/with/key/KEY, params={"value1":"none","value2":"none","value3":"none"})

Thanks


#2

I don’t own one, and haven’t coded for one but here goes.

The issue is the button pressed code is being acted on as it is programed to do.
The button is actually being pressed, there is no condition defined to make it check how long it was pressed. It doesn’t care how long it was pressed only that it was actually pressed.

Then after your 2 seconds the second button held code conditions are met and that code is acted upon.

One way around this is to set a variable to keep track of if the lights are on or off.

L = 1 if lights on and L = 0 if lights are off.

Then do a
“if” button pressed “and” L = 1 do nothing
“if” button pressed “and” L = 0 turn on lights.
Something like that, if elif.

EDIT: That won’t work as I have it, rethinking it and will post back when I figure something out.


#3

I think you may have to use two separate buttons, one for on and one for off?


#4

Thanks for your advice - I wondered if that might be the case. Playing around with it further, I’ve found that setting the hold time to 5s rather than 2s gives enough time for the on command to be executed, then the off meaning that the lights go off. It seems to work roughly 90% of the time, so it looks like it might be that or using two buttons.


#5

The function reference is here, if you haven’t already looked at it.
http://docs.pimoroni.com/buttonshim/

Nothing there jumps out at me as a way around your situation. =(


#6

Looks like the code behind Button SHIM is some kind of eldritch horror that I’ve left in my wake. It makes no effort to differentiate between a “hold” and a “press”. Probably intentionally, but it’s very confusing to distill this behaviour into something that works.

Button presses consist of three parts- the Press, the Hold and the Release. They always happen in this order, and you cannot easily make something triggered by the “Press” contingent on whether or not you do something during the “Hold”. Not without time travel, anyway!

The key is to use the “Release” event to handle your “Press” action. And the “Press” event to reset the state of your system for the next interaction:

has_been_held = False
@buttonshim.on_press(buttonshim.BUTTON_A)
def handler(button, pressed):
    global has_been_held   # Required so that the line below doesn't just create a new  variable in this scope
    has_been_held = False  # Reset our basic state machine to the start

@buttonshim.on_release(buttonshim.BUTTON_A)
def handler(button, pressed):
    if not has_been_held:
        # Perform your "Press" action (lights on?) here

@buttonshim.on_hold(buttonshim.BUTTON_A, hold_time=2)
def handler(button):
    global has_been_held
    has_been_held = True
    # Perform your "Held" action (lights off?) here

Now the flow is as follows:

  1. has_been_held, which tracks if the button has been held, defaults to False
  2. pressing the button sets has_been_held to False starting a new interaction cycle
  3. upon releasing the button, it checks has_been_held and, if true, knows the button has been held down long enough to trigger a hold event, so can skip your release action
  4. if you hold the button, it triggers the hold event, but also sets has_been_held to True to indicate to your release handler that you can ignore the release event.

Whew! I think I confused myself explaining this. This use-case would make a really good example for Button SHIM though, so I’ve raised a ticket to remind us to write one - https://github.com/pimoroni/button-shim/issues/13


Onoff SHIM : No led, power on but does not shutdown
#7

The Sense Hat 5 position switch, they call it a joystick, is pretty well the same deal.
It can detect a press, hold and release. Like you say, you can’t have a release unless there has already been a press. Took me a little while to wrap my head around why they did it the way they did.


#8

That’s fantastic, thank you.

Is there any way the time the button was held for could be used to differentiate? For example, if the button was held for less than 2 seconds (a quick press), send the command for lights on and if it’s held for more than 2 seconds send the command for lights off?

If both used the hold function, would that work? It’s more of a hypothetical question really, what you’ve suggested looks like it’ll work great for me :)


#9

You could start a timer when the user presses the button, and act upon the elapsed time when it’s released So you’d use only on_press and on_release and basically implement your own variable-length mixed on_hold. This could get a little contrived though since you’ll have to remember all the functions!