1

I have a joystick class written in python with gobject, it works great except for one minor problem. Button bounce when I run the code below, it picks up all the button presses multiple times. How can I reduce that to one message per button press with reasonable accuracy?

''' 
Copyright 2009 Jezra Lickter 

This software is distributed AS IS. Use at your own risk. 
If it borks your system, you have  been forewarned. 

This software is licensed under the LGPL Version 3 
http://www.gnu.org/licenses/lgpl-3.0.txt 


for documentation on Linux Joystick programming please see 
http://www.mjmwired.net/kernel/Documentation/input/joystick-api.txt 
''' 

import gobject #needed for sending signals 
import struct #needed for holding chunks of data 

class Joystick(gobject.GObject): 
    '''The Joystick class is a GObject that sends signals that represent 
    Joystick events''' 
    EVENT_BUTTON = 0x01 #button pressed/released 
    EVENT_AXIS = 0x02  #axis moved  
    EVENT_INIT = 0x80  #button/axis initialized  
    #see http://docs.python.org/library/struct.html for the format determination 
    EVENT_FORMAT = "IhBB" 
    EVENT_SIZE = struct.calcsize(EVENT_FORMAT) 

    # we need a few signals to send data to the main 
    '''signals will return 4 variables as follows: 
    1. a string representing if the signal is from an axis or a button 
    2. an integer representation of a particular button/axis 
    3. an integer representing axis direction or button press/release 
    4. an integer representing the "init" of the button/axis 
    ''' 
    __gsignals__ = { 
    'axis' : 
    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, 
    (gobject.TYPE_INT,gobject.TYPE_INT,gobject.TYPE_INT)), 
    'button' : 
    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, 
    (gobject.TYPE_INT,gobject.TYPE_INT,gobject.TYPE_INT)) 
    } 


    def __init__(self,dev_num): 
        gobject.GObject.__init__(self) 
        #define the device 
        device = '/dev/input/js%s' % dev_num 
        #error check that this can be read 
        try: 
            #open the joystick device 
            self.device = open(device) 
            #keep an eye on the device, when there is data to read, execute the read         function 
            gobject.io_add_watch(self.device,gobject.IO_IN,self.read_buttons) 
        except Exception,ex: 
            #raise an exception 
            raise Exception( ex ) 

    def read_buttons(self, arg0='', arg1=''): 
        ''' read the button and axis press event from the joystick device 
        and emit a signal containing the event data 
        ''' 
        #read self.EVENT_SIZE bytes from the joystick 
        read_event = self.device.read(self.EVENT_SIZE)   
        #get the event structure values from  the read event 
        time, value, type, number = struct.unpack(self.EVENT_FORMAT, read_event) 
        #get just the button/axis press event from the event type  
        event = type & ~self.EVENT_INIT 
        #get just the INIT event from the event type 
        init = type & ~event 
        if event == self.EVENT_AXIS: 
            signal = "axis" 
        elif event == self.EVENT_BUTTON: 
            signal = "button" 
        if signal: 
            print("%s %s %s %s" % (signal,number,value,init) ) 
            self.emit(signal,number,value,init) 

        return True 

if __name__ == "__main__": 
    try: 
        j = Joystick(0) 
        loop = gobject.MainLoop() 
        loop.run() 
    except Exception,e: 
        print(e) 
mac
  • 42,153
  • 26
  • 121
  • 131
giodamelio
  • 5,465
  • 14
  • 44
  • 72

2 Answers2

3

There are many ways to debounce buttons. A simple, non-blocking way to do it is to:

  1. Repeatedly check the status of the button over time and...
  2. ...if the button "momentaneous" state is not the one considered "active" then...
  3. ...increment a counter and...
  4. ...if the counter reaches a given threshold, toggle the "active" state of the button.
  5. The counter should be reset each time the "momentaneous" and "active" states are the same

This method requires the check procedure to run at reasonable regular intervals of course, as the reaction time is given by frequency*threshold.

EDIT: I don't have the hardware to actually run this, but the debouncing method should look something like:

if button_now() != button_state:
    debounce_counter += 1
    if debounce_counter == DEBOUNCE_THRESHOLD:
        button_state = not button_state
else:
    debounce_counter = 0

In the above code:

  • button_now() polls the hardware (and return True/False according to if the button circuit is closed or open),
  • button_state is how the rest of the program "sees" the button (again: True/False according to button down or up), `
  • DEBOUNCE_THRESHOLD is the constant you defined according to the formula reaction-time-of-the-button = frequency-of-debouncing-routine * threshold.

HTH!

mac
  • 42,153
  • 26
  • 121
  • 131
  • what does the var `button_state` stand for? – giodamelio Jul 05 '11 at 06:27
  • I'm having a little trouble making this work with my code. The thing is, I have an event system that calls a function when the button is pressed. That code dosen't really work with my setup :(. – giodamelio Jul 05 '11 at 06:42
  • @giodamelio - If you are programming a game, it should be your debounce function to generate the button pressed event for the rest of the program (HID's should be polled when program is idle instead, or you will end up with jitters). The most obvious alternative is to run a *blocking* debouncing routine (like an interrupt): on button pressed, wait X millisecs and query the button again. If the button is still pressed then propagate the event further. But this will block main program execution. All buttons I used have a debouncing time ~20ms. – mac Jul 05 '11 at 06:54
0

Usually, a 1uF/10uF capacitor each between x, y and pushbutton contacts and GND hardware-debounces, so no code is needed.

Systembolaget
  • 2,514
  • 2
  • 21
  • 37