Really it is. Lets consider a possible, naively written, implementation that could be as follows:
from acmepins import GPIO from time import sleep from time import time Button=GPIO('PC17','INPUT') def event_handler(): print ("Status: %d" % Button.digitalRead()) Button.set_edge("both",event_handler) i=0 while True: print i i=i+1 sleep(0.5)
Alas, testing the avove code, we can see, quite often, that simply pressing just once the button, we get more events than expected:
... ... 3 4 5 Status: 0 Status: 1 6 7 Status: 0 Status: 1 8 Status: 0 Status: 1 Status: 1 9 ... ...
this is caused by the mechanical structure built-in into the physical switch: a small and very thin plate that is behaving as a small spring that is bouncing back and further. That, together with the extremely fast reading rate of CPU and its I/O circuit, make the simple software above to read all the bounces as regular events.
So, some smarter technique is in order to avoid that unconvenience.
Here a simple debouncing class is presented:
from acmepins import GPIO from time import time class PushButton(GPIO): OFF = 0 PRESSED = 1 def __init__(self, name): GPIO.__init__(self,name,'INPUT') self.status = PushButton.OFF self.x = 0.0 self.set_edge("both",self.event_handler) def pressed (self): pass def released (self): pass def event_handler(self): if (self.status == PushButton.OFF): if self.digitalRead() == 0: self.status = PushButton.PRESSED self.x = time() self.pressed() elif (self.status == PushButton.PRESSED): if self.digitalRead() == 0: self.x = time() else: if (time() - self.x) > 0.005: self.status = PushButton.OFF self.released(); else: printf ("Fatal error: status unknown: %d" % self.status)
The PushButton class is derived from GPIO in acmepins: that is quite straightforward as in our context a push button is, from the software perspective, a GPIO.
Some purist could argue here that a relation has-a captures better, in general, the concept of push button because, after all, there are even push buttons built upon other mechanism. Nonetheless, we use a simple subclassing.
Our constructor (init method), take care to built the parent object:
def __init__(self, name): GPIO.__init__(self,name,'INPUT') ...
Later in the ctor, the event handler is registered in the parent class:
note that set_edge method is inherited from parent class GPIO and the
OFF and PRESSED are class static variables, in other words, are variables shared by all instances of the class.
pressed and release are two placeholder do-nothing methods, that are recalled when the respective events are detected (more on this later).
event_handler is the callback that implements the debouncing logic. A small state machine is defined here, with two states, OFF and PRESSED. When the push button is in its default position, not surprisingly the state is OFF. Once the user press the button, the handler is recalled (as the GPIO switches from 1 to 0 and we put "both" as first parameter in set_edge) and the first if branch is followed (status == OFF and digitalRead == 0): the state is then changed to PRESSED and the current time is noted in x variable. Lastly the pressed() method is called.
Now, even if, due to the mechanical behavior mentioned, the GPIO changes repeatedly from 0 to 1 and back to 0, as we are in PRESSED state, the pressed() method is ever called again.
Each time the GPIO bounces to 0, the timestamp in x is updated, whilst if it remains to 1 for more than 5 ms (showing that button is back to default unpressed status), the state is changed to off and virtual method released() is called.
So, in order to exploit that class one has to derive (again) a more specific class that is able to do a useful job.
First thing to do is to declare the new class, so it is derived from the parent one:
next a proper contructor has to be defined, where the parent's constructor is explictly called:
def __init__(self, name): PushButton.__init__(self, name)
in our case we passed the string identifying the GIOP pin, up to parent class.
Lastly, the two workers method are redefined accordingly to our purposes:
def pressed (self): print ">>> Button pressed" def released (self): print ">>> Button released"
here we are just printing some informative message. Below you find a working example that, when run on Arietta, is displaying the actions done on the embedded white push button.
Note that in most cases, only the pressed() method is useful, as we need to react immediately to user input.
from gpio_classes import PushButton from time import sleep class MyButtonClass(PushButton): def __init__(self, name): PushButton.__init__(self, name) def pressed (self): print ">>> Button pressed" def released (self): print ">>> Button released" button = MyButtonClass('PC17') i=0 while True: print button.status, i i=i+1 sleep(0.5)
Really, on this specific topic, there is a quite large literature, for a good recap see, for example:
2018 Ⓒ TanzoLab