【问题标题】:How can I send Incoming messages from web-socket to worker thread?如何将传入消息从 web-socket 发送到工作线程?
【发布时间】:2021-08-06 03:27:34
【问题描述】:

我正在尝试在 Python 3.7.3 中使用 web-socket 和 pyqt5 构建一个小型应用程序。基本上,我使用两个网络套接字,一个用于触发唤醒词,另一个用于获取意图。我想将此意图数据(JSON 格式)传递给工作线程,该工作线程将进一步检查条件类型,然后在主 GUI 上显示适当的输出。在这里,用户的输入本质上可以是连续的,也可以不是连续的,但无论输入是否来自用户,GUI 和两个 web-socket 都应该连续并行运行。

# Intents are passed through here
def on_message(ws, message):
    data = json.loads(message)
    return data

def on_error(ws, error):
    print("Error from Intent",error)

def on_close(ws):
    print("**Disconnected**")

def on_open(ws):
    print("**Connected**")

# Same way for Wake Word as did above


#GUI starts here
frame_less = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint)

#GUI Thread class
class ExecuteThread(QThread):
    # creating signals
    output_signal = pyqtSignal(str)
    finished = pyqtSignal()

    def run(self):
        self.process_intent()

    #checking intent name and executing respective action
    def process_intent(self):
        while True:
            #intent method is being called
            self.intent = on_message()
            if ("GetTime" == self.intent["intent"]["name"]):
                say('GetTime Intent Recognized') #text to speech method
                self.output_signal.emit('GetTime Intent Recognized\n') #should display on GUI
                QThread.msleep(3000)
                self.finished.emit()
            elif:
                #do somthing here as above
            else:
                say('Please Try again') #text to speech method
                self.output_signal.emit('Please Try again') #should display on GUI
                QThread.msleep(3000)
                self.finished.emit()


load_gui,_ = loadUiType('virtual_gui.ui')

#GUI main class
class Main(QMainWindow,load_gui):
    def __init__(self,parent=None):
        super(Main,self).__init__(parent)
        self.setupUi(self)
        self.setFixedSize(1200,800)

        # some other stuff

        #Starting Main Event Funaction
        self.Start_Main_Event()

    def Start_Main_Event(self):
        self.thread = ExecuteThread()
        #connecting signals to respective functions
        self.thread.output_signal.connect(self.output_event)
        self.thread.finished.connect(self.thread_finished)
        self.thread.start()

    def thread_finished(self):
        # gets executed if thread finished
        self.text_output.setText('')
        print('finished')

    def output_event(self,text):
        # gets executed on signal
        self.text_output.setText(text)

#displaying gui function
def main_class():
    App = QApplication(sys.argv)
    win = Main()
    win.show()
    win.showFullScreen ()
    QtWidgets.QApplication.setQuitOnLastWindowClosed(True)
    App.aboutToQuit.connect(App.deleteLater)
    App.exec_()
    App.quit()

# Start web socket client and gui
if __name__ == "__main__":
    ws = websocket.WebSocketApp("ws://localhost/intent",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close,
                              on_open = on_open)
    wake = websocket.WebSocketApp("ws://localhost/wake-word",
                              on_message = on_Message,
                              on_error = on_Error,
                              on_close = on_Close,
                              on_open = on_Open)
    t1 = Thread(target = ws.run_forever)
    t2 = Thread(target = wake.run_forever)
    # start gui
    t3 = Thread(target = main_class())
    
    # starting threads 
    t1.start()
    t2.start()
    t3.start()
  
    # wait until all threads finish 
    t1.join()
    t2.join()
    t3.join()
    print ("Exiting Main Thread")

【问题讨论】:

    标签: python multithreading websocket pyqt pyqt5


    【解决方案1】:

    你有很多错误,但最突出的是:

    • 您不能调用 on_message 回调,因为那是 WebSocketApp 任务。您要做的是每次调用 on_message 时执行您的逻辑。

    • 不应在另一个线程中执行 GUI,因为 Qt 禁止它。

    • 即便如此,您在另一个线程中执行 GUI 的尝试还是不成功,因为执行 t3 = Thread(target = main_class())f = main_class() t3 = Thread(target = f) 相同,也就是说,最终 GUI 在主线程中执行,阻塞了所有该行之后的代码。

    逻辑是创建一个QObject,提供一种通过信号公开信息的方式(注意:回调在辅助线程中执行),然后在主线程中实现逻辑。

    import json
    import sys
    import threading
    from functools import cached_property
    
    from PyQt5.QtCore import QObject, Qt, pyqtSignal
    from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
    
    import websocket
    
    # websocket.enableTrace(True)
    
    
    class WebSocketClient(QObject):
        connected = pyqtSignal()
        disconnected = pyqtSignal()
        message_changed = pyqtSignal(str, name="messageChanged")
        error = pyqtSignal(str)
    
        def __init__(self, *, url, parent=None):
            super().__init__()
            self._url = url
    
        @cached_property
        def ws(self):
            return websocket.WebSocketApp(
                self._url,
                on_open=self._on_open,
                on_message=self._on_message,
                on_error=self._on_error,
                on_close=self._on_close,
            )
    
        def start(self):
            threading.Thread(target=self.ws.run_forever, daemon=True).start()
    
        def send(self, message):
            self.ws.send(message)
    
        def close(self):
            self.ws.close()
    
        def _on_open(self, ws):
            self.connected.emit()
    
        def _on_message(self, ws, message):
            self.message_changed.emit(message)
    
        def _on_error(self, ws, error):
            self.error.emit(str(error))
    
        def _on_close(self, ws):
            self.disconnected.emit()
    
    
    class Widget(QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.label = QLabel(alignment=Qt.AlignCenter)
            lay = QVBoxLayout(self)
            lay.addWidget(self.label)
            self.client.message_changed.connect(self.handle_message)
            self.client.start()
            self.resize(640, 480)
    
        @cached_property
        def client(self):
            return WebSocketClient(url="ws://localhost/intent")
    
        def handle_message(self, text):
            try:
                d = json.loads(text)
            except json.decoder.JSONDecodeError as e:
                print("error:", e)
            else:
                if "intent" in d:
                    intent = d["intent"]
                    if "name" in intent:
                        name = intent["name"]
                        self.label.setText(name)
    
        def closeEvent(self, event):
            super().closeEvent(event)
            self.client.close()
    
    
    def main():
        app = QApplication(sys.argv)
    
        w = Widget()
        w.show()
    
        ret = app.exec_()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    

    更简单的解决方案是使用QtWebSockets

    import json
    import sys
    from functools import cached_property
    
    from PyQt5.QtCore import QUrl, Qt, pyqtSignal
    from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
    from PyQt5.QtWebSockets import QWebSocket
    
    
    class Widget(QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.label = QLabel(alignment=Qt.AlignCenter)
            lay = QVBoxLayout(self)
            lay.addWidget(self.label)
            self.client.textMessageReceived.connect(self.handle_message)
            self.client.open(QUrl("ws://localhost/intent"))
            self.resize(640, 480)
    
        @cached_property
        def client(self):
            return QWebSocket()
    
        def handle_message(self, text):
            try:
                d = json.loads(text)
            except json.decoder.JSONDecodeError as e:
                print("error:", e)
            else:
                if "intent" in d:
                    intent = d["intent"]
                    if "name" in intent:
                        name = intent["name"]
                        self.label.setText(name)
    
        def closeEvent(self, event):
            super().closeEvent(event)
            self.client.close()
    
    
    def main():
        app = QApplication(sys.argv)
    
        w = Widget()
        w.show()
    
        ret = app.exec_()
        sys.exit(ret)
    
    
    if __name__ == "__main__":
        main()
    

    【讨论】:

    • 感谢您的详细说明。在我的任务中,我有两个 web-sockets 一个用于唤醒词(ws://localhost/wake),它将唤醒应用程序,然后它将开始记录和处理用户输入,一旦完成,我就可以得到意图网络套接字的意图,否则如果没有唤醒词,我将无法获得任何意图,那么我该如何实现它。
    • @Eric 注意:我只回答你的帖子本身的问题。不要误会,但我不是导师。b 请阅读How to Ask 并查看tour
    • 我明白这一点。我尝试了您的第一个示例,但出现错误:“AttributeError: 'WebSocketClient' object has no attribute 'ws'” 我没有进行任何更改。任何想法?我也尝试实现第二个示例,但它也需要其他依赖项,因为我在 Rpi 3B+(debian buster)上运行它。
    • @Eric 你用的是什么版本的python?
    • 它在 debian buster 上的 python 3.7.3。我也在尝试将其升级到 3.9。
    猜你喜欢
    • 2011-11-18
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多