Reputation: 1
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
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