Eric
Eric

Reputation: 1

How can I send Incoming messages from web-socket to worker thread?

I'm trying to build a small application using web-socket and pyqt5 in Python 3.7.3. Basically, I'm using two web sockets one for triggering wake word and another to get an intent. I want to pass this intent data (which is in JSON format) to the worker thread which will further check the sort of conditions and then display appropriate output on the main GUI. Here, the user's input can, or can't be continuous in nature but GUI along with both web-sockets should run parallel continuously whether input comes from a user or not.

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

Upvotes: 0

Views: 886

Answers (1)

eyllanesc
eyllanesc

Reputation: 244132

You have many errors but among the most outstanding are:

  • You cannot invoke the on_message callback since that is the WebSocketApp task. What you have to do is that every time on_message is invoked do your logic.

  • The GUI should not be executed in another thread since that Qt prohibits it.

  • Even so, your attempt to execute the GUI in another thread is unsuccessful since doing t3 = Thread(target = main_class()) is the same as f = main_class() t3 = Thread(target = f), that is, in the end the GUI is executed in the main thread blocking all the code after that line.

The logic is to create a QObject that provides a way to expose the information through signals (Note: callbacks are executed in a secondary thread) and then implement the logic in the main thread.

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

A simpler solution is to use 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()

Upvotes: 1

Related Questions