使用 Python3 和 QT5 检测机械按钮按下

Posted

技术标签:

【中文标题】使用 Python3 和 QT5 检测机械按钮按下【英文标题】:Detecting Mechanical Button Press with Python3 and QT5 【发布时间】:2018-01-29 23:38:02 【问题描述】:

我有一个使用 Python 5.3 和 PyQt 7.1 开发的应用程序(我认为)。到目前为止,我在 GUI 中的一切工作都很好。我现在需要能够通过 GPIO.add_event_detect 检测两个外部机械按钮的按下。我添加了用于检测信号下降沿的 GPIO 命令,当我尝试执行代码时,我得到“已为此 GPIO 通道启用冲突沿检测”。 我尝试将代码移动到不同的类(它不在while循环中)只是​​为了看看我是否会得到不同的错误。 我正在寻找任何说明如何将 add_event_detect 用于 QT 等基于事件的应用程序的文档或说明。 一如既往,非常感谢! 迈克

编辑: 我尝试了下面的建议,但我遇到了执行问题。下面的代码只有三个类——我没有添加可执行代码,因为我不确定是否有人愿意花时间将 2 个 N.C. 按钮添加到 GPIO 引脚。我希望您只需阅读代码就能看到问题。

ExecuteSession 类工作正常。我正在尝试添加读取 PStop 和 EStop 类中 GPIO 引脚 16 和 18 上两个信号的下降沿的能力。当 ExecuteSession 启动时,我可以看到打印语句“In PStop”和“In EStop”。当我按下按钮时,我看不到其他打印语句。我对 Python 和 Threads 很陌生,如果有任何建议,我将不胜感激。

我确实创建了一个小测试程序来确保 Pi 看到状态转换,它确实做到了。

谢谢

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))

【问题讨论】:

PyQt 7.1 ??????Python 5.3 ?????什么??你在一个非常遥远的未来 显示你的代码。 呃……我老了! Python 3.5 和 QT 5.? 你的问题不清楚,编辑你的问题并添加你尝试过的代码,除了解释你的问题是什么。 如果你用raspberry-pi标签标记它也会有所帮助 【参考方案1】:

您看到的错误可能是由this 问题引起的。虽然您实际上可能没有像那个问题那样在 while 循环中拥有它,但您可能正在多次调用它,因为您已经在 Qt 回调或类似的回调中获得了它。无论如何...关于如何将其与 Qt 集成...

GPIO.add_event_detect 方法需要回调以在 GPIO 端口的状态更改时执行。监控 GPIO 的代码在单独的线程中运行,这意味着回调在单独的线程中运行。这给与 Qt 的集成带来了一些问题,因为您需要确保以线程安全的方式执行此操作(如果您做错了,您最终可能会遇到类似 this 的错误)。

一个简单的解决方案是假设 Qt 信号在从 Python 线程调用时是线程安全的(这是 GPIO 库将使用的)。据我所知,这是一个悬而未决的问题,因为从技术上讲,它们被记录为在从 Python 线程中使用时不是线程安全的,但通常我发现它没问题。如果你想冒你的程序偶尔崩溃的风险,那么这就是你“简单”的方式

我假设你的程序中有一个 QMainWindow 的子类,但这可以添加到任何 Qt 类中:

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))

或者,以“绝对安全”的方式执行此操作需要做更多的工作。在这里,您告诉 GPIO 库将任何事件放入线程安全的 Python 队列中,该队列由发出 Qt 信号的 QThread 线程读出(从 QThreads 使用时,Qt 信号绝对是线程安全的)

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))

无论如何,希望对您有所帮助。如果没有您提供的特定代码,再做任何事情有点困难,但总的来说,这就是您如何将树莓派上的 GPIO 端口与 Qt 事件循环接口。

【讨论】:

三个菠萝,你的指示。我将审查并尝试,但如果不成功,我将创建一个示例代码并发布。再次感谢。 还有一个问题……线程可以产生线程吗?我已经在使用 QThread/Qt 信号,按钮检测会影响执行线程。那么我可以让一个线程产生另一个线程(嵌套?)还是应该分开? 是的,让线程产生其他线程很好。可能如果它是一个QThread 产生一个QThread,那么你需要在构造它时正确设置父级(我认为这只是意味着做second_thread = QThread(self) 或类似的)。 three_pineapples,我根据您的建议添加了代码并解释了正在发生的事情。你能看看,看看你有什么建议吗?这是不可执行的,因为它需要两个连接到 GPIO 的 NC 开关。只是寻找核心审查来寻找代码中的明显错误。谢谢 @Mike 我认为您的问题是缩进错误。 a),PStopEStop 类中的 run 方法缩进太远(它们在__init__ 方法内部,因此不要正确覆盖QThread.run)。 b) 您的 run 方法每次迭代从队列中获取两次。这将获得单独的事件。您应该每次迭代只从队列中读取一次。如果您需要使用该值两次,请从队列中读取一个变量,然后发出/打印该变量。 c) 我认为您在 ExecuteSession.run 中有一些缩进错误,但由于这不是这个问题的一部分,我不能 100% 确定。

以上是关于使用 Python3 和 QT5 检测机械按钮按下的主要内容,如果未能解决你的问题,请参考以下文章

检测笔记本电脑上的电源按钮按下

在 python3 中定义文件描述符 - 使用 pyudev/evdev

Xamarin 表单选择器确定或取消按钮按下检测

在动画期间检测按钮按下

如何检测是不是按下了向上按钮

如何检测是不是使用 sl4a 按下了菜单按钮?