PyQt5和Asyncio

7
能否将一个UDP服务器保持运行作为异步函数接收数据,然后将其传递给同时作为异步函数运行的(PyQt5)小部件?
思路是当服务器接收到的数据更新时,它也会更新小部件。
我已经有一个简单的UDP服务器和(PyQt5)小部件,它们独立地工作良好,但我正在努力将它们结合起来并使它们都异步运行并交换数据(服务器传输数据到小部件)。
[更新]
下面是我正在尝试的小部件。
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow
import asyncio


class Speedometer(QMainWindow):

    angleChanged = QtCore.pyqtSignal(float)

    def __init__(self, parent = None):

        QtWidgets.QWidget.__init__(self, parent)

        self._angle = 0.0

        self._margins = 20

        self._pointText = {0: "40", 30: "50", 60: "60", 90: "70", 120: "80",
                           150:"" , 180: "", 210: "",
                          240: "0", 270: "10", 300: "20", 330: "30", 360: ""}
    def paintEvent(self, event):

        painter = QtGui.QPainter()
        painter.begin(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)

        painter.fillRect(event.rect(), self.palette().brush(QtGui.QPalette.Window))
        self.drawMarkings(painter)
        self.drawNeedle(painter)

        painter.end()

    def drawMarkings(self, painter):

        painter.save()
        painter.translate(self.width()/2, self.height()/2)
        scale = min((self.width() - self._margins)/120.0,
                    (self.height() - self._margins)/60.0)
        painter.scale(scale, scale)

        font = QtGui.QFont(self.font())
        font.setPixelSize(10)
        metrics = QtGui.QFontMetricsF(font)

        painter.setFont(font)
        painter.setPen(self.palette().color(QtGui.QPalette.Shadow))

        i = 0

        while i < 360:

                if i % 30 == 0 and (i <150 or i > 210):
                    painter.drawLine(0, -40, 0, -50)
                    painter.drawText(-metrics.width(self._pointText[i])/2.0, -52,
                                     self._pointText[i])
                elif i <135 or i > 225:
                    painter.drawLine(0, -45, 0, -50)

                painter.rotate(15)
                i += 15

        painter.restore()

    def drawNeedle(self, painter):

        painter.save()
        painter.translate(self.width()/2, self.height()/1.5)
        painter.rotate(self._angle)
        scale = min((self.width() - self._margins)/120.0,
                    (self.height() - self._margins)/120.0)
        painter.scale(scale, scale) 

        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(self.palette().brush(QtGui.QPalette.Shadow))

        painter.drawPolygon(
            QtGui.QPolygon([QtCore.QPoint(-10, 0), QtCore.QPoint(0, -45), QtCore.QPoint(10, 0),
                      QtCore.QPoint(0, 5), QtCore.QPoint(-10, 0)])
            )

        painter.setBrush(self.palette().brush(QtGui.QPalette.Highlight))

        painter.drawPolygon(
            QtGui.QPolygon([QtCore.QPoint(-5, -25), QtCore.QPoint(0, -45), QtCore.QPoint(5, -25),
                      QtCore.QPoint(0, -30), QtCore.QPoint(-5, -25)])
            )

        painter.restore()

    def sizeHint(self):

        return QtCore.QSize(150, 150)

    def angle(self):
        return self._angle

#    @pyqtSlot(float)
    def setAngle(self, angle):

        if angle != self._angle:
            self._angle = angle
            self.angleChanged.emit(angle)
            self.update()

    angle = QtCore.pyqtProperty(float, angle, setAngle)

    @staticmethod
    def mainLoopSpd():
      while True:
            app = QApplication(sys.argv)

            window = QtWidgets.QWidget()
            spd = Speedometer()
            spinBox = QtWidgets.QSpinBox()
            #spd.setAngle(100)
            spinBox.setRange(0, 359)
            spinBox.valueChanged.connect(spd.setAngle)

            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(spd)
            layout.addWidget(spinBox)
            window.setLayout(layout)

            window.show()
            app.exec_()
            #await asyncio.sleep(1)

            sys.exit(app.exec_())

下方是一个UDP套接字端口的实现,同时也会在控制台打印出值。
import socket

class UDPserver:
    def __init__(self, parent= None):

        self.localIP     = "127.0.0.1"
        self.localPort   = 20002
        self.bufferSize  = 1024

        self.UDPServerSocket = socket.socket(family= socket.AF_INET, type=socket.SOCK_DGRAM)  # Create a socket object

        self.UDPServerSocket.bind((self.localIP, self.localPort))                                      

        print("UDP server up and listening")

        self.counter= 1

    @staticmethod
    def  mainLoopUDPserver():

            serv= UDPserver()
        #while(True):

            bytesAddressPair = serv.UDPServerSocket.recvfrom(serv.bufferSize)                     # Receive data from the socket
            message = bytesAddressPair[0]                                               # The output of the recvfrom() function is a 2-element array
                                                                                      # First element is the message
            address = bytesAddressPair[1]                                               # Second element is the address of the sender

            newMsg= "{}".format(message)

            serv.counter=serv.counter+1
            NumMssgReceived = "#Num of Mssg Received:{}".format(serv.counter)


            newMsg= newMsg.replace("'","")
            newMsg= newMsg.replace("b","")
            newMsg= newMsg.split("/")


            eastCoord= float(newMsg[0])
            northCoord= float(newMsg[1])
            vehSpeed= float(newMsg[2])

            agYaw= float(newMsg[3])


            eastCoordStr="East Coordinate:{}".format(newMsg[0])
            northCoordStr="North Coordinate:{}".format(newMsg[1])
            vehSpeedStr= "Vehicle Speed:{}".format(newMsg[2])
            agYawStr="Yaw Angle:{}".format(newMsg[3])


            print(NumMssgReceived)
            print(vehSpeedStr)

以下是调用两个函数的主功能

from speedometer import Speedometer
import asyncio
from pyServer import UDPserver

class mainApp:
    #vel = 0
    def __init__(self):
        self.velo = 0
        self.queue= asyncio.Queue(0)

    async def server(self):

        while True:

            self.velo= UDPserver.mainLoopUDPserver()
            print("THIS IS VELO{}",self.velo)
            #await self.queue.put(self.velo)

            #vel= await self.queue.get()
            #return vel
            #print("ASSDASDSADSD{}",vel)

            await asyncio.sleep(0)
            #print("HI, vel Received={}",self.veloc)
        #return velo

    async def widget(self):
        while True:
            #vel =  await self.queue.get()
            #print("Hola xDDDDDDD", vel)
            print(">>>>>>>>>>>>>>>NextIteration>>>>>>>>>>>>>>")
            await Speedometer.mainLoopSpd()
            await asyncio.sleep(0)


loop= asyncio.get_event_loop()
mApp= mainApp()

loop.create_task(mApp.server())
loop.create_task(mApp.widget())
loop.run_forever()

当我运行它时,它会监听服务器。一旦我开始通过UDP发送数据,它就接收第一个数据片段并打开小部件,这很好,但这会使服务器停止接收任何数据。
正如您在注释中看到的,我也一直在尝试使用Asyncio队列,但我什么都没有得到。
我的理想情况是服务器接收数据并将其传递给小部件,以便其随着传入数据进行更新,但目前我只想让它们独立工作。
谢谢

展示你的代码...FYI Qt 是异步的。 - eyllanesc
@eyllanesc 我已经更新了问题并附上了我的代码,抱歉它有点长,非常感谢您的任何反馈。 - ljcq 09
1个回答

7
你的UDP服务器不是异步运行的,这一点应该很明显。
asyncio 的逻辑是基于事件循环的,而默认情况下 Qt 不支持它,因此你必须使用类似 qasyncpython -m pip install qasync)和 asyncqtpython -m pip install asyncqt)这样的库。
考虑到上述内容,解决方案是: speedometer.py
from PyQt5 import QtCore, QtGui, QtWidgets


class Speedometer(QtWidgets.QWidget):
    angleChanged = QtCore.pyqtSignal(float)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._angle = 0.0

        self._margins = 20

        self._pointText = {
            0: "40",
            30: "50",
            60: "60",
            90: "70",
            120: "80",
            150: "",
            180: "",
            210: "",
            240: "0",
            270: "10",
            300: "20",
            330: "30",
            360: "",
        }

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)

        painter.fillRect(event.rect(), self.palette().brush(QtGui.QPalette.Window))
        self.drawMarkings(painter)
        self.drawNeedle(painter)

    def drawMarkings(self, painter):

        painter.save()
        painter.translate(self.width() / 2, self.height() / 2)
        scale = min(
            (self.width() - self._margins) / 120.0,
            (self.height() - self._margins) / 60.0,
        )
        painter.scale(scale, scale)

        font = QtGui.QFont(self.font())
        font.setPixelSize(10)
        metrics = QtGui.QFontMetricsF(font)

        painter.setFont(font)
        painter.setPen(self.palette().color(QtGui.QPalette.Shadow))

        i = 0

        while i < 360:

            if i % 30 == 0 and (i < 150 or i > 210):
                painter.drawLine(0, -40, 0, -50)
                painter.drawText(
                    -metrics.width(self._pointText[i]) / 2.0, -52, self._pointText[i]
                )
            elif i < 135 or i > 225:
                painter.drawLine(0, -45, 0, -50)

            painter.rotate(15)
            i += 15

        painter.restore()

    def drawNeedle(self, painter):

        painter.save()
        painter.translate(self.width() / 2, self.height() / 1.5)
        painter.rotate(self._angle)
        scale = min(
            (self.width() - self._margins) / 120.0,
            (self.height() - self._margins) / 120.0,
        )
        painter.scale(scale, scale)

        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(self.palette().brush(QtGui.QPalette.Shadow))

        painter.drawPolygon(
            QtGui.QPolygon(
                [
                    QtCore.QPoint(-10, 0),
                    QtCore.QPoint(0, -45),
                    QtCore.QPoint(10, 0),
                    QtCore.QPoint(0, 5),
                    QtCore.QPoint(-10, 0),
                ]
            )
        )

        painter.setBrush(self.palette().brush(QtGui.QPalette.Highlight))

        painter.drawPolygon(
            QtGui.QPolygon(
                [
                    QtCore.QPoint(-5, -25),
                    QtCore.QPoint(0, -45),
                    QtCore.QPoint(5, -25),
                    QtCore.QPoint(0, -30),
                    QtCore.QPoint(-5, -25),
                ]
            )
        )

        painter.restore()

    def sizeHint(self):

        return QtCore.QSize(150, 150)

    def angle(self):
        return self._angle

    @QtCore.pyqtSlot(float)
    def setAngle(self, angle):

        if angle != self._angle:
            self._angle = angle
            self.angleChanged.emit(angle)
            self.update()

    angle = QtCore.pyqtProperty(float, angle, setAngle)


if __name__ == "__main__":
    import sys
    import asyncio
    from asyncqt import QEventLoop

    app = QtWidgets.QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    with loop:
        w = Speedometer()
        w.angle = 10
        w.show()
        loop.run_forever()

server.py

import asyncio

from PyQt5 import QtCore


class UDPserver(QtCore.QObject):
    dataChanged = QtCore.pyqtSignal(float, float, float, float)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._transport = None
        self._counter_message = 0

    @property
    def transport(self):
        return self._transport

    def connection_made(self, transport):
        self._transport = transport

    def datagram_received(self, data, addr):
        self._counter_message += 1
        print("#Num of Mssg Received: {}".format(self._counter_message))
        message = data.decode()
        east_coord_str, north_coord_str, veh_speed_str, ag_yaw_str, *_ = message.split(
            "/"
        )
        try:
            east_coord = float(east_coord_str)
            north_coord = float(north_coord_str)
            veh_speed = float(veh_speed_str)
            ag_yaw = float(ag_yaw_str)
            self.dataChanged.emit(east_coord, north_coord, veh_speed, ag_yaw)
        except ValueError as e:
            print(e)

main.py

import sys
import asyncio

from PyQt5 import QtCore, QtWidgets
from asyncqt import QEventLoop

from speedometer import Speedometer
from server import UDPserver


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.spd = Speedometer()
        self.spinBox = QtWidgets.QSpinBox()
        self.spinBox.setRange(0, 359)
        self.spinBox.valueChanged.connect(lambda value: self.spd.setAngle(value))

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.spd)
        layout.addWidget(self.spinBox)

    @QtCore.pyqtSlot(float, float, float, float)
    def set_data(self, east_coord, north_coord, veh_speed, ag_yaw):
        print(east_coord, north_coord, veh_speed, ag_yaw)
        self.spd.setAngle(veh_speed)


async def create_server(loop):
    return await loop.create_datagram_endpoint(
        lambda: UDPserver(), local_addr=("127.0.0.1", 20002)
    )


def main():
    app = QtWidgets.QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)

    w = Widget()
    w.resize(640, 480)
    w.show()

    with loop:
        _, protocol = loop.run_until_complete(create_server(loop))
        protocol.dataChanged.connect(w.set_data)
        loop.run_forever()


if __name__ == "__main__":
    main()

谢谢,我不知道asyncqt,但我还无法让它运行起来。我得到以下错误在<模块>中 _, protocol = loop.run_until_complete(create_server(loop)) - ljcq 09
我正在使用Python 3.7的PyCharm。 - ljcq 09
追溯(最近的)调用: 文件“C:/Users/u23j37/PycharmProjects/demoCarGUI/DataSim/threadTest2.py”,第130行,在<module>中: main() 文件“C:/Users/u23j37/PycharmProjects/demoCarGUI/DataSim/threadTest2.py”,第124行,在main中: ,protocol = loop.run_until_complete(create_server(loop)) 文件“C:\Users\u23j37\Desktop\Tests\lib\site-packages\asyncqt_init.py”,第309行,在run_until_complete中: return future.result() - ljcq 09
文件“C:/Users/u23j37/PycharmProjects/demoCarGUI/DataSim/threadTest2.py”,第112行,创建服务器 lambda:UDPserver(),local_addr =('127.0.0.1',20002)) 文件“C:\ Users \ u23j37 \ AppData \ Local \ Continuum \ anaconda3 \ lib \ asyncio \ base_events.py”,第1250行,创建数据报端点 sock,protocol,r_addr,waiter) 文件“C:\ Users \ u23j37 \ AppData \ Local \ Continuum \ anaconda3 \ lib \ asyncio \ base_events.py”,第447行,“_make_datagram_transport” 引发NotImplementedError - ljcq 09
@ljcq09 1) 您是否只使用了我提供的代码?我指出这一点是因为根据错误消息,问题发生在第124行,而我的代码行数比那还少。2) 不要使用Anaconda,因为它经常会有错误。3) 即使如此,您也必须安装asyncqt。我已经在Linux上测试过它,并且使用最新版本的库进行了测试(Python 3.8.2,PyQT5 5.14.2等)。 - eyllanesc
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接