Detecting Mechanical Button Press with Python3 and

2019-08-29 23:20发布

问题:

I have an application that is developed in Python 5.3 and PyQt 7.1 (I think). So far I have everything in the GUI working just fine. I now need to be able to detect the pressing of two external mechanical buttons via GPIO.add_event_detect. I added the GPIO command for detecting the falling edge of the signal and when I try to execute the code I get the "Conflicting edge detection already enabled for this GPIO Channel." I tried moving the the code to different classes (it's not in a while loop) just to see if I would get a different error. I am looking for any documentation or instructions that explains how to use the add_event_detect into an event based application like QT.
As always, much thanks! Mike

EDIT: I tried the recommendation below and I am having an issue with execution. The code below is only three classes - I didn't add a executable code because I wasn't sure anybody would want to spend time to add 2 N.C. buttons to GPIO pins. I am hoping you'll be able to see an issue just by reading the code.

The class ExecuteSession works fine. I am trying to add the ability to read the falling edge of two signals on the GPIO pins 16 & 18 in classes PStop and EStop. When the ExecuteSession starts I can see the print statements "In PStop" and "In EStop". When I push the buttons I don't see the other print statement. I am pretty new to Python and Threads and would appreciate any suggestions.

I did create a small test program to make sure the Pi was seeing the state transition, and it did.

Thanks

class PStop(QThread):

    PT_event_detected = pyqtSignal(int)
    def __init__(self):
        QThread.__init__(self)
        self.queue=Queue()
        GPIO.add_event_detect(16, GPIO.FALLING, callback=self.queue.put)
        print("In PStop")

        def run(self):
            while True:
                print("In PStop Run")
                self.PT_event_detected.emit(self.queue.get())
                print("PT EVENT " +str(self.queue.get()))

class EStop(QThread):

    ER_event_detected = pyqtSignal(int)
    def __init__(self):
        QThread.__init__(self)
        self.queue=Queue()
        GPIO.add_event_detect(18, GPIO.FALLING, callback=self.queue.put)
        print("In EStop")

        def run(self):
            while True:
                print("In EStop Run")
                self.ER_event_detected.emit(self.queue.get())                
                print("ER EVENT " +str(self.queue.get()))

class ExecuteSession(QThread):

    PBValueSig = pyqtSignal(int)
    PBValueDone = pyqtSignal(int)
    PBValuePause = pyqtSignal(int)
    PBTimeActual = pyqtSignal(int)

    durComplete = 0

    def __init__(self, dur='', pause='', stopExe =''):
        QThread.__init__(self)
        self.dur = dur
        self.pause = pause
        self.stopExe = stopExe

        self.E_Stop = EStop()
        self.E_Stop.ER_event_detected.connect(self.Ext_Stop_Detect)
        self.E_Stop.start()

        self.P_Stop = PStop()
        self.P_Stop.PT_event_detected.connect(self.Ext_Stop_Detect)
        self.P_Stop.start()

    def stop(self):
        self.stop()

    def __del__(self):
        self.wait()

    def run(self):
        i = 1
        while i <= dur: #self.dur:
            iA = i
            if self.stopExe == True:
                durComplete = i
                i = self.dur + 1 #Complete

            elif self.pause == False: #12/15 added el
                self.PBValueSig.emit(i)
                i = i + 1
                durComplete = i
                time.sleep(1)
        self.PBTimeActual.emit(iA)
        time.sleep(1)
        self.PBValueDone.emit(durComplete)

def Ext_Stop_Detect(self, channel):
    print("CHANNEL " +str(channel))

回答1:

The error you are seeing is likely due to this issue. While you may not actually have it in a while loop like that question, chances are you are calling it multiple times because you've got it in a Qt callback or similar. Anyway...on to how to integrate it with Qt...

The GPIO.add_event_detect method takes a callback to be executed on a state change of the GPIO port. The code monitoring the GPIO runs in a separate thread, which means the callback is run in a separate thread. This poses some problems for integrating with Qt, as you need to make sure you do it in a thread safe way (if you do it wrong, you'll probably end up with an error like this).

A simple solution is to assume that Qt signals are thread safe when called from Python threads (which is what the GPIO library will be using). This is a bit of an open question as far as I'm aware, as technically they are documented as not being thread safe when used from Python threads, but typically I've found it to be OK. If you want to take the risk of your program crashing occasionally, then this is how you do it "simply"

I'll assume you have a subclass of QMainWindow in your program, but this will work added to any Qt class:

class MainWindow(QMainWindow):
    event_detected = pyqtSignal(int)
    def__init__(self, *args, **kwargs):
        QMainWindow.__init__(self, *args, **kwargs)

        self.event_detected.connect(self.on_gpio_event)
        GPIO.add_event_detect(channel, GPIO.BOTH, callback=self.event_detected.emit)

    def on_gpio_event(self, channel):
        # your code here!
        print("An event occurred on channel {}".format(channel))

Alternatively, doing it the "definitely safe" way involves slightly more work. Here you tell the GPIO library to place any events into a thread safe Python queue, which is thread read out by a QThread which emits the Qt signal (Qt signals are definitely thread safe when used from QThreads)

from six.moves.queue import Queue

class RelayThread(QThread):
    event_detected = pyqtSignal(int)
    def __init__(self, *args, **kwargs):
        QThread.__init__(self, *args, **kwargs)
        self.queue = Queue()
        GPIO.add_event_detect(channel, GPIO.BOTH, callback=self.queue.put)

    def run(self):
        while True:
            self.event_detected.emit(self.queue.get())

class MainWindow(QMainWindow):
    def__init__(self, *args, **kwargs):
        QMainWindow.__init__(self, *args, **kwargs)

        self.relay_thread = RelayThread()
        self.relay_thread.event_detected.connect(self.on_gpio_event)
        self.relay_thread.start()

    @pyqtSlot(int)
    def on_gpio_event(self, channel):
        # your code here!
        print("An event occurred on channel {}".format(channel))

Anyway, hope that helps. It's a bit hard to do any more without specific code from you, but in general, this is how you interface the GPIO ports on a raspberry pi with the Qt event loop.