Isuru Liyanage
Isuru Liyanage

Reputation: 23

How to run two infinite loops in PyQt5 GUI?

I`m trying to built a GUI using pyqt5. In the GUI I need to stream a video from the raspberry pi camera via WiFi. And I need to send game-pad inputs to raspberry pi. my code as follows:

import sys,time,math
import cv2
import numpy as np
from PyQt5 import QtCore,QtGui,QtWidgets
from PyQt5 import uic
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QImage,QPixmap
from PyQt5.QtWidgets import QDialog,QApplication
import urllib.request
import threading
import paho.mqtt.client as mqtt
import inputs

m1="0"
broker_ip="computerIP"
url="http://raspberrypiIP/html/cam_pic.php"

#this function get raspberry pi camera video to laptop via WiFi

def url_to_image(url): 
with urllib.request.urlopen(url) as resp:
    image = np.asarray(bytearray(resp.read()),dtype ="uint8")
    image = cv2.imdecode(image,cv2.IMREAD_COLOR)        
return image

def on_connect(client, userdata, flags, rc):
   client.subscribe("arm")
 
def on_message(client, userdata, msg):
    print (msg.payload)

#mqtt is used to send data from laptop to raspberrypi
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(broker_ip, 1883, 60)
threading.Thread(target=client.loop_forever).start()

def Stop1():
    m1="0,0,0,0"
    client.publish("arm",str(m1))
def Forward1():
    m1="0,0,100,100"
    client.publish("arm",str(m1))
def Reverse1():
    m1="0,0,-100,-100"
    client.publish("arm",str(m1))

def cnsl(): # this function detect the gamepad input
    while True:
        events=inputs.get_gamepad()
        for event in events:
            if event.code=="ABS_Y" and event.state>1000:
                Forward1()
            elif event.code=="ABS_Y" and event.state<-1000:
                Reverse1()
            elif event.code=="ABS_Y" and event.state==128:
                Stop1()
      
class test1(QtWidgets.QMainWindow):

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        uic.loadUi("test1.ui",self)
    
    def slot1(self): # this function runs when Pushbutton is clicked
        threadVideo=threading.Thread(target=self.onClicked())
        threadConsol=threading.Thread(target=self.cnsl())
        threadVideo.start()
        threadConsol.start()
        threadVideo.join()
        threadConsol.join()
     
    @pyqtSlot()
# this function display the raspberrypi video in the buited GUI
    def onClicked(self): 
    print ("video")
    while (True):
        img=url_to_image(url)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        qformat=QImage.Format_Indexed8
        if len(img.shape)==3:
            if (img.shape[2])==4:
                qformat=QImage.Format_RGBA888
            else:
                qformat=QImage.Format_RGB888 
        img=QImage(img,img.shape[1],img.shape[0],qformat)
        img=img.rgbSwapped()
        self.label_3.setPixmap(QPixmap.fromImage(img))
          
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

app=QtWidgets.QApplication(sys.argv)
window=test1()
window.show()
app.exec_()

When I run this code, video stream display in the GUI. But I cannot send game-pad inputs. I have tried with 2 buttons also. But both buttons did not work simultaneously. Is any one have any idea??

Upvotes: 2

Views: 431

Answers (1)

eyllanesc
eyllanesc

Reputation: 243897

In the OP code there are trivial errors like:

threading.Thread(target=foo())

since it is equivalent to:

res = foo()
threading.Thread(target=res)

and as you can see the task is not executed in the secondary thread but in the initial thread.

Another mistake is to use join() since this method blocks the execution of the initial thread until what is executed in the secondary thread ends, and obviously that is not what you want.

And another minor error is that it is not necessary to use the loop_forever method in a secondary thread, but just execute the start() function.

In addition, it should be considered that the GUI should not be updated from another thread but send the information to the main thread (where the GUI must be updated) through signals.

Considering the above, the solution is:

import sys
import threading

from PyQt5 import QtCore, QtGui, QtWidgets, uic
import sip

import paho.mqtt.client as mqtt

import inputs
import cv2
import numpy as np
import urllib.request


broker_ip = "computerIP"
url = "http://raspberrypiIP/html/cam_pic.php"


def url_to_image(url):
    with urllib.request.urlopen(url) as resp:
        image = np.asarray(bytearray(resp.read()), dtype="uint8")
        image = cv2.imdecode(image, cv2.IMREAD_COLOR)
    return image


class GamepadManager:
    def __init__(self):
        self.client = mqtt.Client()
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.connect(broker_ip, 1883, 60)

    def start(self):
        self.client.start()
        threading.Thread(target=self._init_bucle, daemon=True).start()

    def on_connect(self, client, userdata, flags, rc):
        self.client.subscribe("arm")

    def on_message(self, client, userdata, msg):
        print(msg.payload)

    def stop_arm(self):
        m1 = "0,0,0,0"
        self.client.publish("arm", m1)

    def forward_arm(self):
        m1 = "0,0,100,100"
        self.client.publish("arm", m1)

    def reverse_arm(self):
        m1 = "0,0,-100,-100"
        self.client.publish("arm", m1)

    def _init_bucle(self):
        while True:
            events = inputs.get_gamepad()
            for event in events:
                if event.code == "ABS_Y" and event.state > 1000:
                    self.forward_arm()
                elif event.code == "ABS_Y" and event.state < -1000:
                    self.reverse_arm()
                elif event.code == "ABS_Y" and event.state == 128:
                    self.stop_arm()


class VideoManager(QtCore.QObject):
    imageChanged = QtCore.pyqtSignal(QtGui.QImage)

    def start(self):
        threading.Thread(target=self._request_video, daemon=True).start()

    def _request_video(self):
        while True:
            img = url_to_image(url)
            qformat = QtGui.QImage.Format_Indexed8
            if len(img.shape) == 3:
                if (img.shape[2]) == 4:
                    qformat = QtGui.QImage.Format_RGBA888
                else:
                    qformat = QtGui.QImage.Format_RGB888
            img = QtGui.QImage(img, img.shape[1], img.shape[0], qformat)
            img = img.rgbSwapped()
            if not sip.isdeleted(self):
                self.imageChanged.emit(img)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        uic.loadUi("test1.ui", self)

    @QtCore.pyqtSlot(QtGui.QImage)
    def update_label(self, image):
        pixmap = QtGui.QPixmap.fromImage(image)
        self.label_3.setPixmap(pixmap)


def main():
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()

    gamepad_manager = GamepadManager()
    video_manager = VideoManager()

    video_manager.imageChanged.connect(w.update_label)

    gamepad_manager.start()
    video_manager.start()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Upvotes: 2

Related Questions